Как работает PredicateBuilder

В C# in a Nutshell есть бесплатный класс PredicateBuilder, который создает предикаты LINQ по частям, доступные здесь . Вот выдержка из метода, который добавляет новое выражение к предикату. Может ли кто-нибудь объяснить это? (я видел этот вопрос, я не хочу общий ответ, как там. Я ищу конкретное объяснение того, как Expression.Invoke и Expression.Lambda строят новое выражение).

public static Expression<Func<T, bool>> And<T> (this Expression<Func<T, bool>> expr1,
                                                     Expression<Func<T, bool>> expr2)
{
    var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
    return Expression.Lambda<Func<T, bool>>
        (Expression.AndAlso (expr1.Body, invokedExpr), expr1.Parameters);
}

person just.another.programmer    schedule 15.07.2012    source источник
comment
Что ты не понимаешь? Эта функция состоит из нескольких частей, и я хотел бы понять, что именно вас смущает.   -  person Oded    schedule 15.07.2012
comment
Что такое invokedExpression? Как параметры текущего выражения (expr1) используются для создания этого invokedExpression? Почему вызов Lambda использует expr1.Body для повторного объединения двух выражений?   -  person just.another.programmer    schedule 15.07.2012
comment
PredicateBuilder — это не C# in Depth, а C# в двух словах   -  person Jon Skeet    schedule 15.07.2012
comment
@JonSkeet Я только что понял иронию в этом. Спасибо за смех.   -  person just.another.programmer    schedule 05.08.2012


Ответы (1)


Допустим, у вас есть:

Expression<Func<Person, bool>> isAdult = p1 => p1.Age >= 18;

// I've given the parameter a different name to allow you to differentiate.
Expression<Func<Person, bool>> isMale = p2 => p2.Gender == "Male";

А затем объедините их с PredicateBuilder

var isAdultMale = isAdult.And(isMale);

Результатом PredicateBuilder является выражение, которое выглядит следующим образом:

// Invoke has no direct equivalent in C# lambda expressions.
p1 => p1.Age >= 18 && Invoke(p2 => p2.Gender == "Male", p1)

Как вы видете:

  1. Полученная лямбда повторно использует параметры первого выражения.
  2. Имеет тело, которое вызывает второе выражение, передавая параметры первого выражения в качестве замены параметров второго выражения. Полученный результат InvocationExpression похож на выражение-эквивалент вызова метода (вызов подпрограммы путем передачи аргументов для параметров).
  3. Ands тело первого выражения и это InvocationExpression вместе, чтобы создать тело результирующей лямбды.

Идея состоит в том, что провайдер LINQ должен понимать семантику этой операции и предпринимать разумные действия (например, генерировать SQL, подобный WHERE age >= 18 AND gender = 'Male').

Однако часто у провайдеров возникают проблемы с InvocationExpressions из-за очевидных сложностей обработки «вложенного вызова выражения внутри выражения».

Чтобы обойти это, LINQKit также предоставляет помощник Expand. По сути, это «встраивает» вызов вызова, заменяя вызов телом вложенного выражения, соответствующим образом заменяя использование параметров вложенного выражения (в данном случае, заменяя p2 на p1). Это должно произвести что-то вроде:

p1 => p1.Age >= 18 && p1.Gender == "Male"

Обратите внимание, что именно так вы бы вручную объединили эти предикаты, если бы сделали это самостоятельно в лямбда-выражении. Но с помощью LINQKit вы можете получить эти предикаты из независимых источников и легко их комбинировать:

  1. Без написания кода выражения "от руки".
  2. Необязательно, таким образом, чтобы потребители результирующей лямбды были прозрачными.
person Ani    schedule 15.07.2012
comment
Если LINQKit способен преобразовывать InvocationExpression во «встроенное» выражение, почему бы не делать это всегда, чтобы сделать класс более удобным для использования? - person just.another.programmer; 15.07.2012
comment
@just.another.programmer: Звучит как вопрос к автору LINQKit .:) Но вот некоторые догадки: 1. And и Or были с первого релиза, а Expand добавили позже (это факт), и автор не хотел нарушать совместимость. 2. Чтобы вы могли различать части, если хотите. 3. Чтобы не заставлять вас платить за функцию, которая вам не нужна (многие провайдеры LINQ прекрасно справляются с вызовами-выражениями). - person Ani; 15.07.2012
comment
Под оплатой я предполагаю, что вы имеете в виду производительность (LINQKit бесплатен). Есть ли серьезное снижение производительности при использовании метода Expand? Есть идеи, как это реализовано? - person just.another.programmer; 15.07.2012
comment
@just.another.programmer: Правильно. Обратите внимание, что эти причины были просто предположениями с моей стороны. Чтобы ответить на ваш последний вопрос, он реализован с использованием посетителя выражений, который просматривает тело выражения, заменяя любые выражения параметров, которые он находит по пути, соответствующим образом. Впоследствии платформа .NET выпустила собственный общедоступный посетитель выражений: msdn.microsoft.com/en-us/library/ - person Ani; 15.07.2012
comment
Здравствуйте @Ani, не могли бы вы рассказать подробнее о 2-м (о Invoke)? Я до сих пор еще не понял. Как можно вычислить выражение `Invoke(p2 =› p2.Gender == Male, p1)`? - person Luke Vo; 03.02.2014
comment
@DatVM: фреймворк / PredicateBuilder не оценивает его - он просто создает дерево с этим узлом - потребитель дерева (обычно поставщик LINQ) должен что-то с ним сделать. - person Ani; 04.02.2014