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.Person | |
FirstName | string |
LastName | string |
DateJoined | DateTime |
UserRank | Int32 |
You have an instance of a generic List
We can start to understand what we need if we decide that we’re going to use the Sort operation on List
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:
http://rogeralsing.com/2009/03/23/linq-expressions-from-and-to-delegates/
Nate Kohari’s use in Community Server 5’s REST API: http://kohari.org/2009/03/06/fast-late-bound-invocation-with-expression-trees/ and the related Rick Strahl entry: http://www.west-wind.com/weblog/posts/653034.aspx
http://www.codeproject.com/KB/recipes/Generic_Sorting.aspx
http://www.codeproject.com/KB/architecture/linqrepository.aspx
No comments:
Post a Comment