Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expression/Statement trees

Updated Question Further Down

I've been experimenting with expression trees in .NET 4 to generate code at runtime and I've been trying to implement the foreach statement by building an expression tree.

In the end, the expression should be able to generate a delegate that does this:

Action<IEnumerable<int>> action = source => 
{
  var enumerator = source.GetEnumerator();
  while(enumerator.MoveNext())
  {
    var i = enumerator.Current;
    // the body of the foreach that I don't currently have yet
  }
}

I've come up with the following helper method that generates a BlockExpression from an IEnumerable:

public static BlockExpression ForEachExpr<T>(this IEnumerable<T> source, string collectionName, string itemName)
{
        var item = Expression.Variable(typeof(T), itemName);

        var enumerator = Expression.Variable(typeof(IEnumerator<T>), "enumerator");

        var param = Expression.Parameter(typeof(IEnumerable<T>), collectionName);

        var doMoveNext = Expression.Call(enumerator, typeof(IEnumerator).GetMethod("MoveNext"));

        var assignToEnum = Expression.Assign(enumerator, Expression.Call(param, typeof(IEnumerable<T>).GetMethod("GetEnumerator")));

        var assignCurrent = Expression.Assign(item, Expression.Property(enumerator, "Current"));

        var @break = Expression.Label();

        var @foreach = Expression.Block(
            assignToEnum,
            Expression.Loop(
                Expression.IfThenElse(
                Expression.NotEqual(doMoveNext, Expression.Constant(false)),
                    assignCurrent
                , Expression.Break(@break))
            ,@break)
        );
        return @foreach;

}

The following code:

var ints = new List<int> { 1, 2, 3, 4 };
var expr = ints.ForEachExpr("ints", "i");
var lambda = Expression.Lambda<Action<IEnumerable<int>>>(expr, Expression.Parameter(typeof(IEnumerable<int>), "ints"));

Generates this expression tree:

.Lambda #Lambda1<System.Action`1[System.Collections.Generic.IEnumerable`1[System.Int32]]>(System.Collections.Generic.IEnumerable`1[System.Int32] $ints)
{
    .Block() {
        $enumerator = .Call $ints.GetEnumerator();
        .Loop  {
            .If (.Call $enumerator.MoveNext() != False) {
                $i = $enumerator.Current
            } .Else {
                .Break #Label1 { }
            }
        }
        .LabelTarget #Label1:
    }
}

This seems to be OK, but calling Compile on that expression results in an exception:

"variable 'enumerator' of type 'System.Collections.Generic.IEnumerator`1[System.Int32]' referenced from scope '', but it is not defined"

Didn't I define it here:

    var enumerator = Expression.Variable(typeof(IEnumerator<T>), "enumerator");

?

Of course, the example here is contrived and doesn't have a practical use yet, but I'm trying to get the hang of expression trees that have bodies, in order to dynamically combine them at runtime in the future.


EDIT: My initial problem was solved by Alexandra, thanks! Of course, I've run into the next problem now. I've declared a BlockExpression that has a variable in it. Inside that expression, I want another expression that references that variable. But I don't have an actual reference to that variable, just its name, because the expression is supplied externally.

var param = Expression.Variable(typeof(IEnumerable<T>), "something");

var block = Expression.Block(
                new [] { param },
                body
            );

The body variable is passed in externally and has no direct reference to param, but does know the name of the variable in the expression ("something"). It looks like this:

var body = Expression.Call(typeof(Console).GetMethod("WriteLine",new[] { typeof(bool) }), 
               Expression.Equal(Expression.Parameter(typeof(IEnumerable<int>), "something"), Expression.Constant(null)));

This is the "code" that this generates:

.Lambda #Lambda1<System.Action`1[System.Collections.Generic.IEnumerable`1[System.Int32]]>(System.Collections.Generic.IEnumerable`1[System.Int32] $something)
{
    .Block(System.Collections.Generic.IEnumerable`1[System.Int32] $something) {
        .Call System.Console.WriteLine($something== null)
    }
}

However, it doesn't compile. With the same error as before.

TLDR: How do I reference a variable by identifier in an expression tree?

like image 561
JulianR Avatar asked Dec 20 '09 18:12

JulianR


People also ask

What is expression tree with example?

Each node in an expression tree is an expression. For example, an expression tree can be used to represent mathematical formula x < y where x, < and y will be represented as an expression and arranged in the tree like structure. Expression tree is an in-memory representation of a lambda expression.

What do you mean by expression tree?

Expression trees represent code in a tree-like data structure, where each node is an expression, for example, a method call or a binary operation such as x < y . You can compile and run code represented by expression trees.

How do you use expression trees?

Expression Trees provide richer interaction with the arguments that are functions. You write function arguments, typically using Lambda Expressions, when you create LINQ queries. In a typical LINQ query, those function arguments are transformed into a delegate the compiler creates.


1 Answers

You problem is that you didn't pass parameters and variables to your block expression. You use them in the "inner" expressions, but the block expression knows nothing about them. Basically, all you need to do is to pass all your parameters and variables to a block expression.

        var @foreach = Expression.Block(
            new ParameterExpression[] { item, enumerator, param },
            assignToEnum,
            Expression.Loop(
                Expression.IfThenElse(
                    Expression.NotEqual(doMoveNext, Expression.Constant(false)),
                    assignCurrent,
                    Expression.Break(@break))
            , @break)
        );
like image 139
Alexandra Rusina Avatar answered Sep 21 '22 06:09

Alexandra Rusina