Почему вы цитируете 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());
}

person Water Cooler v2    schedule 07.05.2015    source источник


Ответы (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). По сути, именно так сохраняется полиморфизм, даже если мы делаем композицию, а не наследование (реализация /interface). Но это означает, что сами методы расширения 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 выражений, чтобы получить "настоящее" предикатное выражение. Да, это довольно накладные расходы для простого запроса LINQ!

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

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

person Luaan    schedule 07.05.2015
comment
Большое Вам спасибо. Я занимался этим уже несколько месяцев. Это, наконец, щелкнуло после того, как я сделал много примеров и много думал об этом за последние несколько месяцев. У меня было несколько теорий о почему, и некоторые из них были верны. Ваш ответ мне тоже помог. - person Water Cooler v2; 27.07.2015