Зачем вам цитировать LambdaExpression?

Я прочитал этот ответ и понял из него конкретный случай, который он выделяет: когда у вас есть лямбда в другой лямбде, и вы не хотите, чтобы внутренняя лямбда случайно компилировалась с внешней. Когда внешнее компилируется, вы хотите, чтобы внутреннее лямбда-выражение оставалось деревом выражений. Там, да, имеет смысл процитировать внутреннее лямбда-выражение.

Но это все, я верю. Есть ли другой вариант использования для цитирования лямбда-выражения?

А если нет, то почему все операторы LINQ, т.е. расширения на IQueryable<T> которые объявлены в Queryable класс цитирует предикаты или лямбды, которые они получают в качестве аргументов, когда они упаковывают эту информацию в MethodCallExpression,

Я попробовал пример (и несколько других за последние пару дней), и нет смысла цитировать лямбду в этом случае.

Вот выражение вызова метода для метода, который ожидает лямбда-выражение (а не экземпляр делегата) в качестве единственного параметра.

Затем я собираю MethodCallExpression завернув его в лямбду.

Но это не компилирует внутренний LambdaExpression (аргумент к GimmeExpression метод), а также. Он оставляет внутреннее лямбда-выражение в виде дерева выражений и не делает его экземпляром делегата.

На самом деле, это работает хорошо без цитирования.

И если я цитирую аргумент, он ломается и выдает ошибку, указывающую, что я передаю неверный тип аргумента GimmeExpression метод.

В чем дело? Что это за цитата?

private static void TestMethodCallCompilation()
{
    var methodInfo = typeof(Program).GetMethod("GimmeExpression", 
        BindingFlags.NonPublic | BindingFlags.Static);

    var lambdaExpression = Expression.Lambda<Func<bool>>(Expression.Constant(true));

    var methodCallExpression = Expression.Call(null, methodInfo, lambdaExpression);

    var wrapperLambda = Expression.Lambda(methodCallExpression);
    wrapperLambda.Compile().DynamicInvoke();
}

private static void GimmeExpression(Expression<Func<bool>> exp)
{
    Console.WriteLine(exp.GetType());
    Console.WriteLine("Compiling and executing expression...");
    Console.WriteLine(exp.Compile().Invoke());
}

1 ответ

Решение

Вы должны передать аргумент как ConstantExpression:

private static void TestMethodCallCompilation()
{
    var methodInfo = typeof(Program).GetMethod("GimmeExpression", 
        BindingFlags.NonPublic | BindingFlags.Static);

    var lambdaExpression = Expression.Lambda<Func<bool>>(Expression.Constant(true));

    var methodCallExpression = 
      Expression.Call(null, methodInfo, Expression.Constant(lambdaExpression));

    var wrapperLambda = Expression.Lambda(methodCallExpression);
    wrapperLambda.Compile().DynamicInvoke();
}

private static void GimmeExpression(Expression<Func<bool>> exp)
{
    Console.WriteLine(exp.GetType());
    Console.WriteLine("Compiling and executing expression...");
    Console.WriteLine(exp.Compile().Invoke());
}

Причина должна быть довольно очевидной - вы передаете постоянное значение, поэтому оно должно быть ConstantExpression, Передавая выражение напрямую, вы явно говорите "и получите значение exp из этого сложного дерева выражений ". И так как это дерево выражений на самом деле не возвращает значение Expression<Func<bool>>, вы получаете ошибку.

Путь IQueryable Работы не имеют к этому никакого отношения. Методы расширения на IQueryable должны сохранять всю информацию о выражениях, включая типы и ссылки ParameterExpressionи тому подобное. Это потому, что они на самом деле ничего не делают - они просто строят дерево выражений. Настоящая работа происходит, когда вы звоните queryable.Provider.Execute(expression), По сути, именно так полиморфизм сохраняется, хотя мы занимаемся композицией, а не наследованием (реализация интерфейса). Но это значит, что IQueryable Сами методы расширения не могут делать ярлыки - они ничего не знают о том, как IQueryProvider на самом деле собирается интерпретировать запрос, поэтому они не могут ничего выбросить.

Однако самое важное преимущество, которое вы получаете от этого, заключается в том, что вы можете составлять запросы и подзапросы. Рассмотрим такой запрос:

from item in dataSource
where item.SomeRelatedItem.Where(subItem => subItem.SomeValue == 42).Count() > 2
select item;

Теперь это переводится примерно так:

dataSource.Where(item => item.SomeRelatedItem.Where(subItem => subItem.SomeValue == 42).Count() > 2);

Внешний запрос довольно очевиден - мы получим Where с данным предикатом. Внутренний запрос, однако, на самом деле будет Call в Whereпринимая фактический предикат в качестве аргумента.

Убедившись, что фактические вызовы Where метод на самом деле переводится в Call из Where метод, оба эти случая становятся одинаковыми, и ваш LINQProvider на несколько проще:)

Я на самом деле написал поставщиков LINQ, которые не реализуют IQueryableи которые на самом деле имеют некоторую полезную логику в таких методах, как Where, Это намного проще и эффективнее, но имеет недостаток, описанный выше - единственный способ обрабатывать подзапросы - это вручную Invoke Call выражения, чтобы получить "реальное" выражение предиката. Yikes - это довольно непросто для простого запроса LINQ!

И, конечно же, это помогает вам составлять разных провайдеров с запросами, хотя я на самом деле не видел (м) примеров использования двух совершенно разных провайдеров в одном запросе.

Что касается разницы между Expression.Constant а также Expression.Quote Сами они кажутся довольно похожими. Принципиальное отличие состоит в том, что Expression.Constant будет рассматривать любые замыкания как фактические константы, а не замыкания. Expression.Quote с другой стороны, сохранит "замыкание" замыканий. Зачем? Потому что сами объекты замыкания также передаются как Expression.Constant:) И с тех пор IQueryable деревья делают лямбды-лямбды-лямбды-ноты [...], вы действительно не хотите терять семантику замыкания в любой момент.

Другие вопросы по тегам