I have a class derived from DynamicObject. I would like to sort that using a property (as passed to a BindingList ApplyCoreSort as a TypeDescriptor). I have tried a number of examples from here and other sites, but many of them are in C# and do not translate well to VB. I found the method from this MSDN blog to be nice and simple, but it fails when translated to VB:
Private Shared Function Sort(source As IEnumerable, [property] As String) As DynamicEntity()
Dim binder__1 = RuntimeBinder.Binder.GetMember(CSharpBinderFlags.None, [property], GetType(Example3), New CSharpArgumentInfo() {CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, Nothing)})
Dim param = Expression.Parameter(GetType(DynamicEntity), "o")
Dim getter = Expression.Dynamic(binder__1, GetType(Object), param)
Dim lambda = Expression.Lambda(Of Func(Of DynamicEntity, Object))(getter, param).Compile()
Return source.Cast(Of DynamicEntity)().OrderBy(lambda).ToArray()
End Function
Primarily because the original code relied on the dynamic type which the object type in VB cannot replace:
Func(Of DynamicEntity, Object)
vs
Func<DynamicObject, dynamic>
The C# equivalent is below:
private IEnumerable<DynamicObject> SortAscending(IEnumerable<DynamicObject> source, PropertyDescriptor property, ListSortDirection direction)
{
CallSiteBinder binder__1 = RuntimeBinder.Binder.GetMember(CSharpBinderFlags.None, property.Name, property.PropertyType, new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) });
ParameterExpression param = Expression.Parameter(typeof(DynamicObject), "o");
DynamicExpression getter = Expression.Dynamic(binder__1, property.PropertyType, param);
dynamic lambda = Expression.Lambda<Func<DynamicObject, dynamic>>(getter, param).Compile();
if (direction == ListSortDirection.Ascending) {
return source.Cast<DynamicObject>().OrderBy(lambda).ToList;
} else {
return source.Cast<DynamicObject>().OrderByDescending(lambda).ToList;
}
}
Unfortunately, translating it back to C# and putting it into another DLL does not work as the compiler complains that the IEnumerable type does not support OrderBy or OrderByDescending.
Can anyone suggest a way to get this working in VB or suggest an alternative example that actually works. Changing the dynamic to object does not work as the dynamic expression compiler refuses to compile the expression at runtime because a string is being returned rather than an object.
I have checked a number of examples and none of them seem to work correctly when trying to sort dynamic objects, and many of them won't compile in VS 2010 VB with .Net Framework 4. Even a way to get the above sort function to work in VB would be appreciated.
You could use a generic expression tree to invoke linq's OrderBy (as in the answer here)?
I've included a VB version of that answer here for convenience (though not as an extension):
Public Shared Function Sort(Of T)(source As IQueryable(Of T), propertyName As String, sortOrder As ListSortDirection) As IQueryable(Of T)
Dim type = GetType(T)
Dim propertyInfo As PropertyInfo = type.GetProperty(propertyName)
Dim parameterExpression As ParameterExpression = Expression.Parameter(type, "p")
Dim propertyAccess As MemberExpression = Expression.MakeMemberAccess(parameterExpression, propertyInfo)
Dim sortExpression As LambdaExpression = Expression.Lambda(propertyAccess, parameterExpression)
Dim sortMethod = If(sortOrder = ListSortDirection.Ascending, "OrderBy", "OrderByDescending")
Dim resultExpression = Expression.[Call](
GetType(Queryable),
sortMethod,
New Type() {type, propertyInfo.PropertyType},
source.Expression,
Expression.Quote(sortExpression)
)
Return source.Provider.CreateQuery(Of T)(resultExpression)
End Function
Related
I always took issue with the inability to var / Dim a variable implicitly when using a Using block to declare a data context which returns the initial value of the variable. For example,
Dim classifications As IEnumerable(Of RT_Classification)
Using dc As New MyDataContext
classifications = dc.RT_Classifications.OrderBy(Function(c) c.Order).ToList()
End Using
the type RT_Classification must be provided explicitly when the variable is declared. Compare with Using not limited to the query alone
Dim classifications = dc.RT_Classifications.OrderBy(Function(c) c.Order).ToList()
in which you get something implicit, analogous to var in c#. But the data context must then contain the declaration, and encompass the entire context of the variable classifications, which is undesirable.
I thought of using a lambda, which basically solves the problems
Dim classifications =
(Function()
Using dc As New MyDataContext
Return dc.RT_Classifications.OrderBy(Function(c) c.Order).ToList()
End Using
End Function).Invoke()
but seems a bit bulky. However, with knowledge of the () shorthand for .Invoke() and an IDE sugestion to remove ( ), I came up with this strange looking, but working code
Dim classifications =
Function()
Using dc As New MyDataContext
Return dc.RT_Classifications.OrderBy(Function(c) c.Order).ToList()
End Using
End Function()
Note the trailing End Function() which was new to me. My question is how long has this been available, and are there any downsides to using it, past potential readability issues?
Note that End Function() isn't a special kind of syntax. It is exactly equivalent to your call to f(), or if you were to wrap the entire expression in parentheses.
Prior to parsing the () shorthand, the compiler compiles the entire lambda expression, moves it to a separate method and replaces it in your code with a delegate pointing to that method. So when the compiler resumes parsing your code, all it sees is something along the lines of: ThisIsADelegate().
This becomes apparent if you decompile your application using a tool like ILSpy.
Original code:
Public Class TheseAreLambdas
Dim values As String() = {"This", "word", "is", "the", "longest"}
Dim classifications =
Function()
Return values.OrderBy(Function(s) s.Length).ToList()
End Function()
Public Sub DoSomething()
'I used DirectCast just to reduce mess in the decompiled code. Not necessary otherwise.
MessageBox.Show(DirectCast(classifications, List(Of String)).Count.ToString())
End Sub
End Class
Decompiled code:
public class TheseAreLambdas
{
private string[] values;
private object classifications;
public TheseAreLambdas()
{
/*
All variable initializations are automatically moved to the constructor.
*/
//Our array of values.
values = new string[5]
{
"This",
"word",
"is",
"the",
"longest"
};
//Our lambda expression, moved to a method called "_Lambda$__1", wrapped in
//a compiler-generated delegate and invoked on the spot (notice the parenthesis on the end).
classifications = new VB$AnonymousDelegate_1<List<string>>(_Lambda$__1)();
}
public void DoSomething()
{
MessageBox.Show(((List<string>)classifications).Count.ToString());
}
[CompilerGenerated]
private List<string> _Lambda$__1() //Our lambda expression used for the "classifications" variable.
{
return values.OrderBy((string s) => s.Length).ToList();
}
[CompilerGenerated]
private static int _Lambda$__2(string s)
{
return s.Length;
}
}
I have a function SelectNext() that takes a collection parameter (of type IEnumerable) and it selects the next item in the collection and return that item.
'BaseListTypes is an Enum
Function SelectNext(listType As BaseListTypes, lst As IEnumerable(Of Object)) As Object
Dim res As Object
'function body here....
Return res
End Function
The above function works great for any List(Of T) where T is an object or string.
However it fails when I pass it a List(Of My_STRUCTURE) (custom structure I have, which contains 3 string variables)
Obviously since Structure is like integer and other base types are a value types.
While objects are reference types. I can see why I am getting an error at runtime.
My question is, is there a better way than just overloading my function into something like:
Function SelectNext(listType As BaseListTypes, lst As IEnumerable(Of My_STRUCTURE)) As Object
You can go with generic impelementation like SelectNext<T>
class Program
{
Program()
{
//SelectNext_Old(new List<object>(0)); it works
//SelectNext_Old(new List<Point>(0)); id doesn't work
SelectNext(new List<object>(0));
SelectNext(new List<Point>(0));
}
public object SelectNext_Old(BaseListTypes listType, IEnumerable<object> lst)
{
return null;
}
public object SelectNext<T>(BaseListTypes listType, IEnumerable<T> lst)
{
return null;
}
}
I dont know VB but I guess you can get the idea!
In theory, this should work for any type, whether it be reference type or value type:
Function SelectNext(Of T)(listType As BaseListTypes, lst As IEnumerable(Of T)) As T
Dim res As T
'function body here....
Return res
End Function
I'd need more information to know for sure though. Your question is actually a bit vague. For one thing, what does "selects the next item in the collection" actually mean? You should edit your question to make it more complete, with a better description of what you're doing and what errors occur and where.
I am kind of new to VBA and I am stuck at the simple task of getting an element out of my ArrayList at a spesified index.
Set resultList = CreateObject("System.Collections.ArrayList")
resultList.Add element1
resultList.Add element2
resultList.Add element3
resultList.Add element4
return resultList.get(2) '<-- Not working
I have checked the documentation of ArrayList but failed to find such a "get(index)" function:
https://msdn.microsoft.com/de-de/library/system.collections.arraylist(v=vs.110).aspx
Thanks in advance.
If you can legally use return resultList.Item(2) and have working code, then you're not using VBA but VB.NET.
In VBA a function's return value needs to be assigned using the function's identifier:
Public Function GetFoo() As String
GetFoo = "Hello"
End Function
In VB.NET a function's return value is returned using the Return keyword:
Public Function GetFoo() As String
Return "Hello"
End Function
And if you're using VB.NET, then you have absolutely no reason whatsoever to use CreateObject to create an ArrayList.
And if you're using .NET 2.0 or greater, you have absolutely zero reason to use an ArrayList anyway.
Use a generic List(Of SomeType) and enjoy type safety.
That said, Item is an ArrayList's default property, so you could just as well do this:
Return resultList(2)
Ok, I found the solution myself:
resultList.Item(2)
Someone (w69rdy) in Stack Overflow helped me out with a great example to handle DB output, that could potentially be NULL, passed into a function. The problem is I can understand the method as written in C# but I am having a problem understanding how to rewrite the method in VB.NET. The method uses generics and I am lost. Here is the method written in C# ..
public T ParseValue<T>(System.Data.SqlClient.SqlDataReader reader, string column)
{
T result = default(T);
if (!reader.IsDBNull(reader.GetOrdinal(column)))
result = (T)reader.GetValue(reader.GetOrdinal(column));
return result;
}
How is this written in VB.NET? How does the method signature change when returning a generic type?
You can use the C# to VB.NET converter which produces the following results:
Public Function ParseValue(Of T)(reader As System.Data.SqlClient.SqlDataReader, column As String) As T
Dim result As T = Nothing
If Not reader.IsDBNull(reader.GetOrdinal(column)) Then
result = DirectCast(reader.GetValue(reader.GetOrdinal(column)), T)
End If
Return result
End Function
Additionally:
I would recommend the following resource to help know syntax differences between VB.NET and C#. It has a section on Generics:
VB.NET and C# Comparison
Public Function ParseValue(Of T)(reader As System.Data.SqlClient.SqlDataReader, _
column As String) As T
Dim result As T = Nothing
If Not reader.IsDBNull(reader.GetOrdinal(column)) Then
result = DirectCast(reader.GetValue(reader.GetOrdinal(column)), T)
End If
Return result
End Function
From C# to VB.NET converter.
I've been reading a book which is in C#. I'm a VB.NET developer (and a very junior one at that) and I'm having a lot of trouble with the following code that contains lots of things I've never seen before. I do have a basic knowledge of Lambda Expressions.
public List<T> SortByPropertyName(string propertyName, bool ascending)
{
var param = Expression.Parameter(typeof(T), "N");
var sortExpression = Expression.Lambda<Func<T, object>>
(Expression.Convert(Expression.Property(param, propertyName),
typeof(object)), param);
if (ascending)
{
return this.AsQueryable<T>().OrderBy<T, object>(sortExpression).ToList<T>();
}
else
{
return this.AsQueryable<T>().OrderByDescending<T, object>(sortExpression).ToList<T>
}
}
Could anybody illuminate me as to what this code is doing and what concepts are being used?
I am also trying to convert this code into VB.NET with little luck so any help there would be appreciated as well.
Overall, the code is sorting something (presumably a list?) by the specified property name in either ascending or descending order. There must already be a generic type T specified somewhere else in this class.
The code creates a new ParameterExpression by calling Expression.Parameter, then passes that parameter into the Expression.Lambda function, which creates a new lambda expression.
This expression is then used to sort the list by calling the OrderBy or OrderByDescending function (the choice depending on the ascending parameter) and returns the sorted list as a new List<T>.
I'm not in front of Visual Studio at the moment, but this should be a sufficiently close translation to VB for you.
Public Function SortByPropertyName(ByVal propertyName as String, ByVal ascending as Boolean) as List(Of T)
Dim param = Expression.Parameter(GetType(T), "N")
Dim sortExpression = Expression.Lambda(Of Func(Of T, Object))(Expression.Convert(Expression.Property(param, propertyName), GetType(Object)), param)
If ascending Then
return Me.AsQueryable(Of T).OrderBy(Of T, Object)(sortExpression).ToList()
Else
return Me.AsQueryable(Of T).OrderByDescending(Of T, Object)(sortExpression).ToList()
End If
End Function
This should work:
Return Me.AsQueryable.OrderBy(sortExpression).ToList
See also: http://www.codeproject.com/KB/recipes/Generic_Sorting.aspx