Поделиться через


Behind the Scenes of LINQ - Part 2

Last time we started by translating a LINQ query

List<int> ints = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8 };

var filteredInts = from i in ints where i > 5 select i;

into an extension method (as the C# compiler does in background):

 var filteredInts = ints.Where(i => i > 5);

But where do we go from here? What is the strange => arrow doing within my method call?!
This is basically a different notation for an anonymous method.

Let's start with a default .NET 2.0 code doing a calculation with the use of anonymous methods.

         delegate int CalcDelegate(int a, int b);

        static void Main(string[] args)
        {
            CalcDelegate add = delegate(int a, int b)
            {
                return a + b;
            };
            var result = add(10, 5); // 15
        }

With C# 3.0 we could change this code into the new notion of a lambda expression:

             CalcDelegate add = (a, b) =>
            { // Statement Body
                return a + b;
            };
            var result = add(10, 5); // 15

The => arrow indicates a lambda expression. This expression has a statement body - meaning it has curly brackets and some C# code within. You can skip the delegate keyword, the types of a and b will automatically be inferred regarding the signature of the delegate.

You could do anything within the statement body, like:

             CalcDelegate add = (a, b) =>
            {
                Console.WriteLine("Hello World");
                ...
                return a + b;
            };

If you only have a return statement within your expression's body, you can also switch to an "expression body" (instead of statement body).

The lambda expressions then look like this:

 CalcDelegate add = (a, b) => a + b; // Expression Body

The return statement and the curly brackets are then removed.

If you disassemble the lambda expressions (either with statement or with expression body) you'll find out, that the compiler generates an anonymous method out of the expression automatically in background.image

With the expression body you can basically use every statement which returns a type similar to the return value-type of the delegate.

Instead of creating your own delegate type, you can use a predefined generic delegate called Func. This delegate takes one return type and up to four parameter types. To get the same delegate as our custom CalcDelegate you could use:

 Func<int, int, int> add = ...;
 Func<string, int, bool> check = (text, length) => text.Length == length;

The second method checks, whether a passed string's (first parameter "text") length matches a passed int (second parameter length). The result is returned as boolean.

What, if you want to analyze a lambda expression to transform it into a SQL query for instance? You could use reflection of course to go over the code of the generated anonymous method, but this is rather difficult.

So for lambda expressions with an expression body (like the one above) there is a special notation called expression trees.

If you specify

 Expression<Func<int, int, int>> addExp = (a, b) => a + b;

this is going to be changed into an expression tree of some delegate in background:

       ParameterExpression par1;
      ParameterExpression par2;
      Expression<Func<int, int, int>> addExp = Expression.Lambda<Func<int, int, int>>(
         Expression.Add(
            par1 = Expression.Parameter(typeof(int), "a"),
            par2 = Expression.Parameter(typeof(int), "b")
         ), par1, par2);

What you get is logical view of your lambda expression (called tree), which you can then analyze by using it's Body property.

If you want to execute the expression tree later as a delegate , you need to compile it to source code by calling:

      Func<int, int, int> addDel = addExp.Compile();
     var result = addDel(1, 2);  // 3

So when you specify a lambda expression (with an expression body), the compiler will either produce an anonymous method, or an expression tree out of it, depending on what the target type requires (if it is a delegate or Expression<delegate>).

In my opinion this is pretty neat!

While LINQ-To-XML and LINQ-To-Objects use Func<T..> as parameter, LINQ-To-SQL uses Expression<Func<T..>>, so that the tree can be translated into a SQL Statement.

Comments