Lambda Expressions and Expression Trees

Lambda Expressions and Expression Trees

  • Comments 4

Hi there!

My name is Tim, and I'm the dev lead for the VB compiler team. Recently Amanda convinced me to blog on the VB team blog rather then my own blog for a variety of reasons, and so here I am. My current plan is that I'll post VB related postings here, and more compiler implementation/technology/fun facts on my own blog (ie, things that don't relate to the VB language/features).

Recently my article on lambda expressions got published on MSDN magazine. For this blog post, I'm going to expand on some of the ideas in that article, so feel free to check out the article if some of the ideas here don't make sense yet.

Lambda expressions are really cool not only because of the reasons stated in the article, but also because it's possible to capture the contents of a lambda expression in a readable form and treat it as data rather than executable code.

For example, the following line of code turns a lambda expression into a callable delegate:

Dim f As Func(Of Integer, Integer) = function(x) x * 2
Dim r = f(10)

In this code sample, the first line creates a lambda expression and does the magic to assign it to a callable delegate f, and the second line invokes f and returns 20.

This is all cool, and I hope my article highlights some of the reasons why I think this is cool. What's even more cool is that you can actually assign a lambda expression into a special variable type and the compiler does some more magic for you:

Dim e As Expression(Of Func(Of Integer, Integer)) = function(x) x * 2

In this code sample, e is not a callable delegate. Rather, it's an expression tree. An expression tree is a data representation of the lambda expression in a form that is easy for you to read and reason about the lambda expression in.

If you look through the value of e in the debugger, you'll see that you can easily determine that the expression was a multiplication operation of variable called x of type integer with a constant integer 2.

By default, query operators that extend IEnumerable take lambda expressions. Query operators that extend IQueryable take expression trees. Therefore, when you issue a query over a type that is queryable, the compiler will generate an expression tree rather than a lambda expression.

Scott, one of the developers on my team, has written an excellent article as well on some of these ideas. He has a great diagram there showing the expression tree representation of a few lambda expressions.

What can you do with expression trees?

In general, you can do 2 things:

  1. Reason about the data in the expression tree, and do custom processing over it. You can also convert the data in the expression tree into another domain (such as XML).
  2. Use the .Compile() method to turn the expression tree into a delegate so that you can execute it.
  3. Build queries with dynamic conditions: see Jonathan's (our PM) excellent post. He has a great example of how you can manipulate the expression trees and build custom conditions in your lambda expressions.

Item 2 is interesting, because it means that you can write a lambda expression into an expression tree, reason about it, etc, and then turn it into a callable delegate.

Dim e As Expression(Of Func(Of Integer, Integer)) = function(x) x * 2
Dim f = e.Compile
Dim r = f(10)

In this example, the value of r is 20, just like the previous example where we assigned the lambda directly to a delegate type.

Why is this interesting?

Without expression trees, its impossible to describe expressions in a descriptive format; the only description of them is IL, which is too low level to re-create the user's intent for the expression. Rather, expression trees provide a high level tree representation of an expression, so that you can easily understand the user's intent for the expression.

Here are some uses of expression trees that may be interesting:

  1. Reason about expressions, then convert them to delegates and call them.
  2. Convert expressions into SQL so that they can be executed by a server (this is what LINQ to SQL does).
  3. Convert expressions into XML and write them to disk.
  4. Convert expressions into a custom format to send over a network protocol to a server that can then rebuild the expression tree from the format, recreate the expression, and reason about it and/or execute it.

Over the next several posts, I'm going to blog specifically about the expression trees that the VB compiler generates, and some differences between C# trees and VB trees in order to maintain VB semantics. I will also provide tips on things you can do to be sure that any library that you write that consumes expression trees will provide the best experience for VB developers.

Tim

Leave a Comment
  • Please add 4 and 8 and type the answer here:
  • Post
  • PingBack from http://msdnrss.thecoderblogs.com/2007/09/12/lambda-expressions-and-expression-trees/

  • Please!

    Fix the bug with VB lambda expressions in Beta 2.

    These same line of code comile to very different IL and return different results in VB and C#.

    I don't understab why, but VB compiler generates closures for this code and returns wrong results.

    C#

    delegate TReturn SelfApplicable0<TReturn>(SelfApplicable0<TReturn> self);

    SelfApplicable0<Func<Func<Func<int, int>, Func<int, int>>, Func<int, int>>> YC = y => f => x => f(y(y)(f))(x);

    VB

    Delegate Function SelfApplicable0(Of TReturn)(ByVal self As SelfApplicable0(Of TReturn)) As TReturn

    Dim YC As SelfApplicable0(Of Func(Of Func(Of Func(Of Integer, Integer), Func(Of Integer, Integer)), Func(Of Integer, Integer))) = Function(y) Function(f) Function(x) f(y(y)(f))(x)

  • Hi there,

    Thanks for posting this suggestion.  The behavior is actually by design, and not a bug at all.  If you run the code you'll notice the output is exactly the same as C#.

    VB's closures are the same as C#'s display classes. In the C# example, there is a nesting level of 2 display classes. In VB, we have a nesting level of 3 closures. C# chose to optimize one level of display classes away by creating a static function for lambdas that don't lift variables. VB chose not to do this optimization because these classes would then not be hostable in the SQL CLR.

    Hope that helps,

    Jonathan Aneja

    PM, VB Team

  • Last week I got the chance to visit the Toronto .NET User Group and give a talk on all the great new

Page 1 of 1 (4 items)