c#表达式中的闭包变量捕获问题

时间:2013-01-29 14:26:47

标签: c# lambda closures

我有一个使用表达式树创建委托的函数。在这个表达式中,我使用从传入函数的多个参数中捕获的变量。实际的表达式树相当大,例如:

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();
}

然后我在将该函数传递给另一个要使用的函数之前从另一个方法调用此方法。完成所有操作后,我想访问在表达式树中更新的父项内容。

一切似乎都在编译,我的表达看起来还不错,但是当我运行它时,我出现(虽然我不能确定)在访问parent变量时(在表达式/闭包内)获取空引用异常。

我想我想知道我是做错了什么,或者这是否可行,以及了解发生了什么的提示。我似乎无法在方法中找到任何悬挂的(?)局部变量,所以我想知道它们是否被捕获了?

谢谢, 标记

2 个答案:

答案 0 :(得分:16)

  

我似乎无法在方法中找到任何悬挂的局部变量,所以我想知道它们是否被捕获了?

看起来您正在通过“手动”调用工厂方法来自己构建表达式树lambda。编译器不知道你正在做什么;它只是看到方法调用。如果你想要提升当地人,那么你将不得不(1)让编译器为你做,通过使重写lambda,或者(2)自己提升他们

那是:

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

编译器会重写lambda并为你提升它,就像你说的那样:

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

c通常成为常量表达式。

但如果你说

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

编译器不知道你在做需要提升x的事情; x不在lambda表达式中。如果您正在使用工厂,编译器会假设您知道自己在做什么,并且不会重写它。你必须自己提升它。

答案 1 :(得分:0)

我认为您正在寻找Expression.Quote,它支持Lambda表达式中的变量捕获。基本上,内部LambdaExpression(将引用捕获的变量)需要包含在Expression.Quote(...)调用中。

此处的示例和讨论:What does Expression.Quote() do that Expression.Constant() can’t already do?