Локальные деревья переменных и выражений
Я изучаю деревья выражений в С#.
Я застрял на некоторое время:
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 });