In Item 42 I briefly discussed how query providers like LINQ to SQL examine queries before execution and translate those queries into their native format. LINQ to Objects, in contrast, implements queries by compiling lambda expressions into methods and creating delegates that access those methods. It’s plain old code, just accessed through delegates.
LINQ to SQL (and any other query provider) performs this magic by asking for query expressions in the form of a System.Linq.Expressions.Expression object. Expression is an abstract base class that represents an expression. One of the classes derived from Expression is System.Linq.Expressions.Expression<TDelegate> where TDelegate is a delegate type. Expression<TDelegate> represents a lambda expression as a data structure. It can be analyzed by using the Body, NodeType, and Parameters properties. Furthermore, it can be compiled into a delegate using the Expression<TDelegate>.Compile() method. That makes Expression<TDelegate> more general than Func<T>. Func<T> is a delegate that can be invoked. Expression<TDelegate> can be examined, or compiled and then invoked in the normal way.
When your design includes storing lambda expressions, you’ll have more options open to you by storing those lambda expressions using Expression<T>. You don’t lose any features; you simply have to compile the expression before invoking it:
Expression<Func<int, bool>> compound = val => (val % 2 == 1) && (val > 300); Func<int, bool> compiled = compound.Compile(); Console.WriteLine(compiled(501));
The Expression class provides methods that allow you to examine the logic of an expression. You can examine an expression tree and see exactly what logic made up the expression. The C# team provides a reference implementation of examining an expression with the C# samples delivered with VS 2008. The ExpressionTreeVisualizer sample, which includes source code, provides code that examines each node type in an expression tree and displays the contents of that node. It’s a recursive implementation that recursively visits each sub-node in the tree, which is how you would examine each node in a tree in an algorithm to visit and modify each node.
Working with expressions and expression trees instead of functions and delegates can be a better choice because expressions have quite a bit more functionality: you can convert Expressions to Funcs, you can traverse expression trees, which means you can create modified versions of the expressions. Building new algorithms at runtime are is possible with Expressions, it’s much, much harder with Funcs.
Where this habit helps you is that you can combine expressions later using code. That builds an expression tree that contains multiple clauses. After building the code, you can call Compile() and create the delegate when you need it.
Here is one way to combine two expressions to form a larger expression:
Expression<Func<int, bool>> IsOdd = val => val % 2 == 1; Expression<Func<int, bool>> IsLargeNumber = val => val > 300; InvocationExpression callLeft = Expression.Invoke(IsOdd, Expression.Constant(5)); InvocationExpression callRight = Expression.Invoke(IsLargeNumber, Expression.Constant(5)); BinaryExpression Combined = Expression.MakeBinary(ExpressionType.And, callLeft, callRight); // Convert to a typed expression: Expression<Func<bool>> typeCombined = Expression.Lambda<Func<bool>>(Combined); Func<bool> compiled = typeCombined.Compile(); bool answer = compiled();
The code above creates two small expressions and combines them into a single expression. Then, it compiles the larger expression and executes it. If you’re familiar with either CodeDom, or Reflection.Emit, the Expression APIs can provide similar metaprorgramming capabilities. You can visit expressions, create new expressions, compile them to delegates, and finally execute them.
Working with expression trees is far from simple. Expressions are immutable. That makes creating a modified version of an expression a rather extensive undertaking. You need to traverse every node in the tree, and either copy it to the new tree, or replace the existing node with a different expression that produces the same type of result. Several implementations of expression tree visitors have been written, as samples and as open source projects. I won’t add yet another version here. A web search for "Expression Tree Visitor" will find several implementations.
The System.Linq.Expressions namespace contains a rich grammar that you can use to build algorithms at runtime. You can construct your own expressions by building the complete expression from its components. The following code executes the same logic as the previous example, but it builds the lambda expression in code:
// The lambda exression has one parameter: ParameterExpression parm = Expression.Parameter(typeof(int), "val"); // We’ll use a few integer constants: ConstantExpression threeHundred = Expression.Constant(300, typeof(int)); ConstantExpression one = Expression.Constant(1, typeof(int)); ConstantExpression two = Expression.Constant(2, typeof(int)); // Creates (val > 300) BinaryExpression largeNumbers = Expression.MakeBinary(ExpressionType.GreaterThan, parm, threeHundred); // creates (val % 2) BinaryExpression modulo = Expression.MakeBinary(ExpressionType.Modulo, parm, two); // builds ((val % 2) == 1), using modulo BinaryExpression isOdd = Expression.MakeBinary(ExpressionType.Equal, modulo, one); // creates ((val % 2) == 1) && (val > 300), // using isOdd and largeNumbers BinaryExpression lambdaBody = Expression.MakeBinary(ExpressionType.AndAlso, isOdd, largeNumbers); // creates val => (val % 2 == 1) && (val > 300) // from lambda body and parameter. LambdaExpression lambda = Expression.Lambda(lambdaBody, parm); // Compile it: Func<int, bool> compiled = lambda.Compile() as Func<int, bool>; // Run it: Console.WriteLine(compiled(501));
Yes, using Expressions to build your own logic is certainly more complicated than creating the expression from the Func<> definitions shown earlier. This kind of meta programming is an advanced topic. It’s not the first tool you should reach from in your toolbox.
Even if you don’t build and modify expressions, libraries you use might. You should consider using Expression<> instead of Func<> when your lambda expressions are passed to unknown libraries where the implementation may make use of the expression tree logic to translate your algorithms into a different format. Any IQueryProvider, such as LINQ to SQL would perform that translation.
In addition, you may create your own additions to your type that would be better served by expressions than by delegates. The justification is the same: You can always convert expressions into delegates, but you can’t go the other way.
You may find that delegates are an easier way to represent lambda expressions, and conceptually they are. Delegates can be executed. Most C# developers understand them, and they will often provide all the functionality you need. However, if your type will be storing expressions, passing those expressions to other objects not under your control, or if you will be composing expressions into more complex constructs, consider using expressions instead of func. You’ll have a richer set of APIs that will enable you to modify those expressions at runtime, and invoke them after you have examined them for your own internal purposes.