Wednesday, September 16, 2009

Building General Purpose Lambda Expressions

I was fascinated a while back to find the use of the Expression class to build general purpose lambda expressions while researching the use the Repository pattern and the Entity Framework. I didn’t look into it much at the time, but have recently had the time to dig a little deeper.

By general purpose lambda I mean using classes in the System.Linq.Expressions namespace to build a run-time lambda from parameters that let you manipulate which class, method, and properties are used to build the expression executed as a delegate.

Let’s say you have an entity with several properties by which a user may want to sort a list. For example a Person with FirstName, LastName, DateJoined, and UserRank.


Person


FirstName string
LastName string
DateJoined DateTime
UserRank Int32




You have an instance of a generic List and want to sort it according to a user’s selection on a column. Of course you can probably think of some other ways to get this done, but it’s a good opportunity for a general purpose lambda (and actually just a neat thing to do).

We can start to understand what we need if we decide that we’re going to use the Sort operation on List, and specifically the overload that takes a Comparison delegate. If we were to hard-code our delegates we would have:


    public enum SortBy
{
FirstName
,LastName
,DateJoined
,UserRank
}

Comparison<Person> fnameCompare =
(x, y) = x.FirstName.CompareTo(y.FirstName);
Comparison<Person> lnameCompare =
(x, y) = x.LastName.CompareTo(y.LastName);
Comparison<Person> dateCompare =
(x, y) = x.DateJoined.CompareTo(y.DateJoined);
Comparison<Person> rankCompare =
(x, y) = x.UserRank.CompareTo(y.UserRank);



We could do a switch statement and sort with the correct delegate. However, with the availability of a "CompareTo" method on every type on which we want to sort, and a common delegate type with basically the same expression syntax, it looks like we can do better.

The goal of our expression building will be to build the expression of the form (x,y) = x.[FieldName].CompareTo(y.[FieldName]) where x and y are Person type input arguments and the CompareTo operation is being executed against the underlying field type.

In order to build such an expression we will need parameters for the delegate, which in this case is an x and y parameter of the type Person since the Comparison delegate signature is

public delegate int Comparison<T>(
 T x,
T y
)


var xparam = Expression.Parameter(typeof(Person), "x");
var yparam = Expression.Parameter(typeof(Person), "y");



We are also going to need references to the field on each delegate parameter so we can run the CompareTo operation on fields involved:



var xprop =
Expression.Property(xparam, this.SortBy.ToString());
var yprop =
Expression.Property(yparam, this.SortBy.ToString());



All 4 of these statements are creating expressions. In the first case we are creating instances of ParameterExpression, and in the second case MemberExpression. I found it was most helpful to my understanding that each concrete type of Expression is adding a node to the parse tree. As such, picking the right type of Expression to represent what you want is key to building the pieces of your overall lambda expression.


Note in the given expression, a local field with the SortBy enum has values equal to the field names on the target type – this is a stand-in for expressing a string field name.

Since we now have our delegate parameters, and the participating fields from those parameters, we can build the call to CompareTo. Since we want to add a method call to the tree, we will build a MethodCallExpression using the static Expression.Call helper method:



BindingFlags flags = BindingFlags.FlattenHierarchy |
BindingFlags.ExactBinding |
BindingFlags.NonPublic |
BindingFlags.Public |
BindingFlags.Instance;


MethodInfo info = xprop.Type.GetMethod("CompareTo", flags, null, new Type[] { xprop.Type }, null);


var methodCall = Expression.Call(xprop,info,yprop);


I used an overload that takes a MethodInfo because the underlying FindMethod used in the Expression class does not specify "ExactBinding" and fails because it also finds the CompareTo overload with the object type parameter. Also note that the ParameterExpression instance xprop has a handy Type field that provides the type of the referenced field on the MemberExpression type to which it was bound.

With these 5 expressions ready to go all we need to do is generate a lambda expression of the appropriate delegate type and compile it:


var sortExpression = Expression.Lambda<Comparison<Person>>(
methodCall
, xparam
, yparam);

return sortExpression.Compile();


Place this code in a method with a return type of Comparison<Person> which could be called to retrieve the delegate and sort your list:

myList.Sort(GetSortExpression());

Submit this story to DotNetKicks