Search code examples
c#expression-trees

Expression trees - invoke lambda during loop leads to variable leaking


I have found little issue with expression trees and would like to know whether such is a bug or a feature. I have this code. The first function assigns one to a variable and returns it.

static class Functions 
{
    static public Expression<Func<int>> GetOne()
    {
        //Returns something like this () => {int x; return x +1;}
        var variable = Expression.Variable(typeof(int));
        var f = Expression.Lambda<Func<int>>(
                            Expression.Block(
                            new[] { variable },
                            Expression.Assign(variable, Expression.Add(variable, Expression.Constant(1)))
        ));
        return f;
    }
    
    static public Expression<Func<int>> ApplyTenTimes()
    {
        var i = Expression.Variable(typeof(int));
        var breakLabel = Expression.Label();
        var f = GetOne();
        var loop = Expression.Lambda<Func<int>>(
        Expression.Block(
            new[] { i },
            Expression.Block(
            new Expression[] {
            Expression.Loop(Expression.Block(
                        Expression.IfThen(Expression.Equal(i, Expression.Constant(10)),          Expression.Break(breakLabel)),
                Expression.PostIncrementAssign(i),
                Expression.Call(typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }),   Expression.Call( Expression.Invoke(f),typeof(int).GetMethod("ToString", new Type[0]))
                    ))),
            Expression.Label(breakLabel),
            Expression.Invoke(f)
            })));
    
        return loop;
    }
}
f = Functions.GetOne().Compile();
IEnumerable<int> a = Enumerable.Range(0, 9).Select(_ => f()).ToList();
//Prints 1
Console.WriteLine(f());

f = Functions.ApplyTenTimes().Compile();
//Prints 1, ..., 10, 1
Console.WriteLine(f());

I expected the f always prints 1 as 0 is a default value for int.


Solution

  • If you try to get an interpreted delegate when calling Compile:

    var f = Functions.ApplyTenTimes().Compile(preferInterpretation: true);
    Console.WriteLine(f());
    

    Then it behaves as you'd expect - "1" is printed eleven times.

    Based on this fact, this is highly likely to be a bug in the expression compiler when compiling a loop expression, since the delegate should have the same behaviour regardless of whether it is interpreted or compiled.