views:

513

answers:

1

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?

+3  A: 

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)
        );
Alexandra Rusina
This threw me for a loop for a little while. I think the first parameter into Block is called "Parameters" but what it should really be called is "VariableDeclarations". This was the key to solving it though thanks!
justin.m.chase