Проблема с захватом переменной замыкания в выражении С#

У меня есть функция, которая создает делегат, используя деревья выражений. Внутри этого выражения я использую переменную, захваченную из нескольких параметров, переданных функции. Фактическое дерево выражений довольно велико, например:

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 (внутри выражения/закрытия).

Думаю, я хотел бы знать, что я делаю что-то неправильно или это возможно, а также советы для понимания того, что происходит. Кажется, я не могу найти какие-либо поднятые (?) Локальные переменные внутри метода, поэтому мне интересно, вообще ли они захватываются?

Спасибо, Марк

Ответ 1

Кажется, я не могу найти какие-либо поднятые локальные переменные в методе, поэтому мне интересно, вообще ли они будут захвачены?

Похоже, вы строите дерево выражений lambda самостоятельно, "вручную" вызываете методы factory. Компилятор не знает, что вы делаете; он просто видит вызовы методов. Если вы хотите, чтобы локальные жители были подняты, вам придется либо (1) заставить компилятор сделать это за вас, сделав переписывание лямбды, либо (2) тащить себя.

То есть:

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

компилятор перезаписывает лямбду и поднимает ее для вас, как будто вы сказали:

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 не находится внутри лямбда-выражения. Если вы используете фабрики, компилятор предполагает, что вы знаете, что делаете, и не возитесь с переписыванием. Вам придется поднять его самостоятельно.

Ответ 2

Я думаю, что вы ищете Expression.Quote, который поддерживает захват переменной в выражениях Lambda. В основном внутренний LambdaExpression (который будет ссылаться на захваченные переменные) должен быть заключен в вызов Expression.Quote(...).

Примеры и обсуждение здесь: Что Expression.Quote() делает этот Expression.Constant() уже не работает?