What in the world does this code do? (C#) - vb.net

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

Related

Looking for lambda invocation shorthand documentation

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;
}
}

How can I disambiguate '=' symbol in VB.NET, in a lambda function

I am using Dapper to query a flat list of items from a database, into a POCO class as follows:
Public Class Node
Public Property Name As String
Public Property ParentNodeName As String
Public Property Children As IEnumerable(Of Node)
End Class
I am trying to use the accepted answer to this question, in order to create a tree out of the flat list.
The only caveat is that I am using VB.NET.
I have tried it a straightforward port of the C# solution:
nodes.ForEach(Function(n) n.Children = nodes.Where(Function(ch) ch.ParentNodeName = n.Name).ToList)
but it does not compile with the error
Error BC30452 Operator '=' is not defined for types 'List(Of Node)' and 'List(Of Node)'.
The = symbol is interpreted as an equality operator, while I meant to use the assignment operator.
I have pasted the C# code into the telerik converter, and the converted code is:
Private Shared Function BuildTree(ByVal items As List(Of Category)) As IEnumerable(Of Category)
items.ForEach(Function(i) CSharpImpl.__Assign(i.Categories, items.Where(Function(ch) ch.ParentId = i.Id).ToList()))
Return items.Where(Function(i) i.ParentId Is Nothing).ToList()
End Function
Private Class CSharpImpl
<Obsolete("Please refactor calling code to use normal Visual Basic assignment")>
Shared Function __Assign(Of T)(ByRef target As T, value As T) As T
target = value
Return value
End Function
End Class
It uses an helper class to solve this issue, but suggests a refactor to avoid this.
Hence the questions:
Is there a general way to disambiguate equality = and assignment = in VB.NET, without resorting to an helper class and a specific function to assignement
In this specific case, is there a simple refactor I can use to get rid of the issue?
That's because of VB.Net distinction between functions and subroutines.
Instead of
nodes.ForEach(Function(n) n.Children = nodes.Where(Function(ch) ch.ParentNodeName = n.Name).ToList)
use
nodes.ForEach(Sub(n) n.Children = nodes.Where(Function(ch) ch.ParentNodeName = n.Name).ToList)
When you use Function, the lambda expression is expected to return a value; and in your case it looks like it wants to return a boolean.
But you want to use a lambda expression that does not return anything (in your case, you want an assignment), you have to use Sub.

How to overload a function when you only need to change a parameter data type

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.

VB Linq sorting using dynamic expressions on dynamic objects

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

Returning Generic type in VB.NET

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.