Search code examples
c#lambdaclosures

Issue with closure variable capture in c# expression


I have a function which creates a delegate using expression trees. Within this expression I use a variable captured from multiple parameters passed in to the function. The actual expression tree is rather large so as an example:

Delegate GenerateFunction<T>(T current, IList<T> parents) {
    var currentExpr = Expression.Parameter(typeof(T), "current");
    var parentsExpr = Expression.Parameter(parents.getType(), "parents");
    var parameters = new List<ParameterExpression>();
    //...
    return Expression
        .Lambda(
            Expression.Block(new List<ParameterExpression> { parentsExpr, currentExpr }, ...),
            parameters.ToArray())
        .Compile();
}

I then invoke this method from another method before passing that function to another function to use. Once that's all done I want to access the content of parents which gets updated within the expression tree.

Everything seems to compile, and my expression looks ok, but when I run it I appear (although I can't really be sure) to be getting null reference exceptions when accessing the parents variable (inside the expression/closure).

I can't find any hoisted (?) local variables within the method so I'm wondering whether they're being captured at all?


Solution

  • I don't seem to be able to find any hoisted local variables within the method so I'm wondering whether they're being captured at all?

    It looks like you are building the expression tree lambda yourself, by "manually" calling the factory methods. The compiler has no idea that that's what you're doing; it just sees method calls. If you want locals to be hoisted then you're going to have to either (1) get the compiler to do it for you, by making it rewrite the lambda, or (2) hoist 'em yourself.

    That is:

    int x = 123;
    Expression<Func<int>> ex = ()=>x; 
    

    the compiler rewrites the lambda and hoists it for you, as though you'd said:

    Closure c = new Closure();
    c.x = 123;
    Expression<Func<int>> ex = ()=>c.x; 
    

    Where c typically becomes a Constant expression.

    But if you say

    Expression<Func<int>> ex = Expression.Lambda( ...something that uses x ... );
    

    the compiler has no idea that you're doing something where it needs to hoist x; x is not inside a lambda expression. If you're using the factories, the compiler assumes you know what you're doing and doesn't mess around with rewriting it. You'll have to hoist it yourself.