Локальные деревья переменных и выражений

Я изучаю деревья выражений в С#.

Я застрял на некоторое время:

string filterString = "ruby";
Expression<Func<string, bool>> expression = x => x == filterString;

Как я могу построить это выражение с помощью кода? Нет примера, как захватить локальную переменную. Это легко:

Expression<Func<string, bool>> expression = x => x == "ruby";

Это будет:

ParameterExpression stringParam = Expression.Parameter(typeof(string), "x");
Expression constant = Expression.Constant("ruby");
BinaryExpression equals = Expression.Equal(stringParam, constant);
Expression<Func<string, bool>> lambda1 =
    Expression.Lambda<Func<string, bool>>(
        equals,
        new ParameterExpression[] { stringParam });

Отладчик печатает следующее для (x = > x == filterString):

{x = > (x == значение (Predicate.Program + < > c__DisplayClass3).filterString)}

Спасибо, что пролил свет на эту тему.

Ответы

Ответ 1

Захват локальной переменной фактически выполняется путем "подъема" локальной переменной в переменную экземпляра класса, генерируемого компилятором. Компилятор С# создает новый экземпляр дополнительного класса в соответствующее время и изменяет любой доступ к локальной переменной в доступ к переменной экземпляра в соответствующем экземпляре.

Таким образом, дерево выражений тогда должно быть доступом к полю в экземпляре - и сам экземпляр предоставляется через ConstantExpression.

Самый простой способ работы с созданием деревьев выражений обычно заключается в создании чего-то подобного в выражении лямбда, а затем посмотреть на сгенерированный код в Reflector, повернув уровень оптимизации вниз, чтобы Reflector не преобразовал его в лямбда-выражения.

Ответ 2

Этот код обертывает выражение в блоке замыкания, которое обрабатывает локальную переменную как константу.

 string filterString = "ruby";

 var filterStringParam = Expression.Parameter(typeof(string), "filterString");
 var stringParam = Expression.Parameter(typeof(string), "x");

 var block = Expression.Block(
 // Add a local variable.
 new[] { filterStringParam },
 // Assign a constant to the local variable: filterStringParam = filterString
 Expression.Assign(filterStringParam, Expression.Constant(filterString, typeof(string))),
 // Compare the parameter to the local variable
 Expression.Equal(stringParam, filterStringParam));

 var x = Expression.Lambda<Func<string, bool>>(block, stringParam).Compile();

Ответ 3

Старый вопрос, но я пришел к нему, когда пытался сделать что-то подобное для выражения Linq-to-entities (L2E). В этом случае вы не можете использовать Expression.Block, поскольку он не может быть обработан SQL.

Вот явный пример после ответа Джона, который будет работать с L2E. Создайте вспомогательный класс, чтобы содержать значение фильтра:

class ExpressionScopedVariables
{
    public String Value;
}

Создайте дерево таким образом:

var scope = new ExpressionScopedVariables { Value = filterString};
var filterStringExp = Expression.Constant(scope);
var getVariable = typeof(ExpressionScopedVariables).GetMember("Value")[0];
var access = Expression.MakeMemberAccess(filterStringExp, getVariable);

И затем замените константу в исходном коде выражением доступа к члену:

BinaryExpression equals = Expression.Equal(stringParam, access);
Expression<Func<string, bool>> lambda1 =
    Expression.Lambda<Func<string, bool>>(
        equals,
        new ParameterExpression[] { stringParam });