Cook Computing

Expression<TDelegate> Class

February 3, 2009 Written by Charles Cook

Brad Abram's post Avoiding Custom Delegates, which I linked to in an earlier post, also mentions the Expression<TDelegate> class.

Expression<...> represents function definitions that can be compiled and subsequently invoked at runtime but can also be serialized and passed to remote processes.

Expression<TDelegate> takes a delegate type as its generic parameter and is created by assigning from a lambda expression.


using System.Collections.ObjectModel;

namespace System.Linq.Expressions
{
  // Summary:
  //     Represents a strongly typed lambda expression as a data structure in the
  //     form of an expression tree. This class cannot be inherited.
  //
  // Type parameters:
  //   TDelegate:
  //     The type of the delegate that the System.Linq.Expressions.Expression<TDelegate>
  //     represents.
  public sealed class Expression<TDelegate> : LambdaExpression
  {
    // Summary:
    //     Compiles the lambda expression described by the expression tree into executable
    //     code.
    //
    // Returns:
    //     A delegate of type TDelegate that represents the lambda expression described
    //     by the System.Linq.Expressions.Expression<TDelegate>.
    public TDelegate Compile();
  }
}

For example:


Expression<Func<int, bool>> exprTree = num => num < 5;

The compiler is implemented such that when it has to compile an assignment like this to Expression>TDelegate<, instead of compiling the lambda expression to IL code it constructs an "expression tree", a type of abstract syntax tree (AST). This sample code from MSDN illustrates what happens:


Expression<Func<int, bool>> exprTree = num => num < 5;

// Decompose the expression tree.
ParameterExpression param = (ParameterExpression)exprTree.Parameters[0];
BinaryExpression operation = (BinaryExpression)exprTree.Body;
ParameterExpression left = (ParameterExpression)operation.Left;
ConstantExpression right = (ConstantExpression)operation.Right;

Console.WriteLine("Decomposed expression: {0} => {1} {2} {3}",
                      param.Name, left.Name, operation.NodeType, right.Value);
/*
console output is:

Decomposed expression: num => num LessThan 5

*/

The Compile method of Expression<TDelegate> can be called to create a delegate of the instance's generic type:


Func<int, bool> func = exprTree.Compile();
Assert.AreEqual(true, func(3));

The advantage of using Expression<TDelegate> is that if the methods of an API take instances of this instead of delegates, the implementation of the API can optimize the processing of the expression tree at runtime. For example, in Linq to SQL many of the methods of the IQueryable interface take Expression<TDelegate> so that optimized SQL can be generated at runtime. This can make a huge difference to the performance of Linq expressions.

Note that Expression<TDelegate> can only be assigned a lambda expression with an expression body. You cannot assign a lambda expression or an anonymous delegate:


// does not compile
Expression<Func<int, bool>> exprTree2 = num => { return num < 5; };
    
// does not compile
Expression<Func<int, bool>> exprTree3 = delegate(int num) { return num < 5; };