FREE BOOK

Chapter 12 - Delegates and Lambda Expressions

Posted by Addison Wesley Free Book | LINQ October 13, 2009
C# achieves the same functionality using a delegate, which encapsulates methods as objects, enabling an indirect method call bound at runtime.

Expression Trees

Lambda expressions provide a succinct syntax for defining a method inline within your code. The compiler converts the code so that it is executable and

bool swap = first < second;
if (swap)
{
swapCount++;
}
return swap;
}
}
LocalsDisplayClass_00000001 locals =
new __LocalsDisplayClass_00000001();
locals.swapCount=0;
BubbleSort(items, locals.__AnonymousMethod_00000000);|
Console.WriteLine("Items were swapped {0} times.",
locals.swapCount);

callable later, potentially passing the delegate to another method. One feature for which it does not offer intrinsic support, however, is a representation of the expression as data-data that may be traversed and even serialized.

Consider the lambda expression in the following code:

persons.Where( person => person.Name.ToUpper() == "INIGO MONTOYA"); Assuming that persons is an array of Persons, the compiler compiles the lambda expression to a Func<string, bool> delegate type and then passes the delegate instance to the Where() method. Code and execution like this work very well. (The Where() method is an IEnumerable extension method from the class System.Linq.Enumerable, but this is irrelevant within this section.)

What if persons was not a Person array, but rather a collection of Person objects sitting on a remote computer, or perhaps in a database? Rather than returning all items in the persons collection, it would be preferable to send data describing the expression over the network and have the filtering occur remotely so that only the resultant selection returns over the network. In scenarios such as this, the data about the expression is needed, not the compiled CIL. The remote computer then compiles or interprets the expression data.

Interpreting is motivation for adding expression trees to the language. Lambda expressions that represent data about expressions rather than compiled code are expression trees. Since the expression tree represents data rather than compiled code, it is possible to convert the data to an alternative format-to convert it from the expression data to SQL code (SQL is the language generally used to query data from databases) that executes on a database, for example. The expression tree received by Where() may be converted into a SQL query that is passed to a database, for example (see Listing 12.22).

Listing 12.22: Converting an Expression Tree to a SQL where Clause

persons.Where( person => person.Name.ToUpper() == "INIGO MONTOYA");
select * from Person where upper(Name) = 'INIGO MONTOYA';

Recognizing the original Where() call parameter as data, you can see that it is made up of the following:

  1. The call to the Person property, Name
  2. A call to a string method called ToUpper()
  3. A constant value, "INIGO MONTOYA"
  4. An equality operator, ==

The Where() method takes this data and converts it to the SQL where clause by iterating over the data and building a SQL query string. However, SQL is just one example of what an expression tree may convert to. Both a lambda expression for delegates and a lambda expression for an expression tree are compiled, and in both cases, the syntax of the expression is verified at compile time with full semantic analysis. The difference, however, is that a lambda expression is compiled into a delegate in CIL, whereas an expression tree is compiled into a data structure of type System. Linq.Expressions.Expression. As a result, when a lambda expression is an expression lambda, it may execute-it is CIL instructions for what the runtime should do. However, if the lambda expression is an expression tree, it is not a set of CIL instructions, but rather a data structure. Although an expression tree includes a method that will compile it into a delegate constructor call, it is more likely that the expression tree (data) will be converted into a different format or set of instructions.

System.Linq.Enumerable versus System.Linq.Queryable

Let us consider an example that highlights the difference between a delegate and an expression tree. System.Linq.Enumerable and System.Linq.Queryable are very similar. They each provide virtually identical extension methods to the collection interfaces they extend (IEnumerable and IQueryable, respectively). Consider, for example, the Where() method from Listing 12.22. Given a collection that supports IEnumerable, a call to Where() could be as
follows:

persons.Where( person => person.Name.ToUpper() ==
"INIGO MONTOYA");

Conceptually, the Enumerable extension method signature is defined on IEnumerable<TSource> as follows:

public IEnumerable<TSource> Where<TSource>(
Func<TSource, bool> predicate);

However, the equivalent Queryable extension on the IQueryable< TSource> method call is identical, even though the conceptual Where() method signature (shown) is not:

public IQueryable<TSource> Where<TSource>(
Expression<Func<TSource, bool>> predicate);

The calling code for the argument is identical because the lambda expression itself does not have type until it is assigned/cast. Enumerable's Where() implementation takes the lambda expression and converts it to a delegate that the Where() method's implementation calls. In contrast, when calling Queryable's Where(), the lambda expression is converted to an expression tree so that the compiler converts the lambda expression into data. The object implementing IQueryable receives the expression data and manipulates it. As suggested before, the expression tree received by Where() may be converted into a SQL query that is passed to a database.

Examining an Expression Tree

Capitalizing on the fact that lambda expressions don't have intrinsic type, assigning a lambda expression to a System.Linq.Expressions.Expression< TDelegate> creates an expression tree rather than a delegate. In Listing 12.23, we create an expression tree for the Func<int, int, bool>. (Recall that Func<int, int, bool> is functionally equivalent to the ComparisonHandler delegate.) Notice that just the simple act of writing an expression to the console, Console.WriteLine(expression) where expression is of type Expression<TDelegate>, will result in a call to expression's ToString() method). However, this doesn't cause the expression to be evaluated or even to write out the fully qualified name of Func<int, int, bool> (as would happen if we used a delegate instance). Rather, displaying the expression writes out the data (in this case, the expression code) corresponding to the value of the expression tree.

Listing 12.23: Examining an Expression Tree

using System;
using System.Linq.Expressions;
class Program
{
    static void Main()
    {
        Expression<Func<int, int, bool>> expression;
        expression = (x, y) => x > y;
        Console.WriteLine("-------------{0}-------------",
        expression);
        PrintNode(expression.Body, 0);
        Console.WriteLine();
        Console.WriteLine();
        expression = (x, y) => x * y > x + y;
        Console.WriteLine("-------------{0}-------------",
        expression);
        PrintNode(expression.Body, 0);
        Console.WriteLine();
        Console.WriteLine();
    }
    public static void PrintNode(Expression expression,
    int indent)
    {
        if (expression is BinaryExpression)
            PrintNode(expression as BinaryExpression, indent);
        else
            PrintSingle(expression, indent);
    }
    private static void PrintNode(BinaryExpression expression,
    int indent)
    {
        PrintNode(expression.Left, indent + 1);
        PrintSingle(expression, indent);
        PrintNode(expression.Right, indent + 1);
    }
    private static void PrintSingle(
    Expression expression, int indent)
    {
        Console.WriteLine("{0," + indent * 5 + "}{1}",
        "", NodeToString(expression));
    }
    private static string NodeToString(Expression expression)
    {
        switch (expression.NodeType)
        {
            case ExpressionType.Multiply:
                return "*";
 
            case ExpressionType.Add:
                return "+";
            case ExpressionType.Divide:
                return "/";
            case ExpressionType.Subtract:
                return "-";
            case ExpressionType.GreaterThan:
                return ">";
            case ExpressionType.LessThan:
                return "<";
            default:
                return expression.ToString() +
                " (" + expression.NodeType.ToString() + ")";
        }
    }
}

In Output 12.3, we see that the Console.WriteLine() statements within Main() print out the body of the expression trees as text.

The output of the expression as text is due to conversion from the underlying data of an expression tree-conversion similar to the Print- Node() and NodeTypeToString() functions, only more comprehensive. The important point to note is that an expression tree is a collection of data, and by iterating over the data, it is possible to convert the data to another format. In the PrintNode() method, Listing 12.23 converts the data to a horizontal text interpretation of the data. However, the interpretation could be virtually anything.

OUTPUT 12.3:
------------- (x, y) => x > y -------------
x (Parameter)
>
y (Parameter)
------------- (x, y) => (x * y) > (x + y) -------------
x (Parameter)
*
y (Parameter)
>
x (Parameter)
+
y (Parameter)

Using recursion, the PrintNode() function demonstrates that an expression tree is a tree of zero or more expression trees. The contained expression trees are stored in an Expression's Body property. In addition, the expression tree includes an ExpressionType property called NodeType where ExpressionType is an enum for each different type of expression. There are numerous types of expressions: BinaryExpression, ConditionalExpression, LambdaExpression (the root of an expression tree), Method- CallExpression, ParameterExpression, and ConstantExpression are examples. Each type derives from System.Linq.Expressions.Expression. Generally, you can use statement lambdas interchangeably with expression lambdas. However, you cannot convert statement lambdas into expression trees. You can express expression trees only by using expression lambda syntax.

Total Pages : 9 56789

comments