How does PredicateBuilder work
Asked Answered
P

1

37

C# in a Nutshell has a free class called PredicateBuilder which constructs LINQ predicates piece by piece available here. Here's an extract of the method which adds a new expression to the predicate. Could someone explain it? (I have seen this question, I don't want a general answer like there. I am looking for a specific explanation of how Expression.Invoke and Expression.Lambda build the new expression).

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);
}
Pottage answered 15/7, 2012 at 9:37 Comment(4)
What don't you understand? There are several parts to this function and I would like to understand what exactly your find confusing.Controversy
What is an invokedExpression? How are the parameters of the current expression (expr1) being used to produce that invokedExpression? Why does the Lambda call use expr1.Body to combine the two expressions again?Pottage
It's not "C# in Depth" that has PredicateBuilder - it's "C# in a Nutshell"Pluckless
@JonSkeet I just realized the irony in this. Thanks for the laugh.Pottage
S
61

Let's say you have:

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";

And then combine them with PredicateBuilder

var isAdultMale = isAdult.And(isMale);

What PredicateBuilder produces is an expression that looks like this:

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

As you can see:

  1. The resulting lambda reuses the parameters of the first expression.
  2. Has a body that invokes the second expression by passing the parameters of the first expression as a replacement for the second expression's parameters. The resulting InvocationExpression is sort of like the expression-equivalent of a method-call (calling a routine by passing in arguments for parameters).
  3. Ands the first expression's body and this InvocationExpression together to produce the body of the resulting lambda.

The idea is that the LINQ provider should be able to understand the semantics of this operation and take a sensible course of action (e.g. generate SQL like WHERE age >= 18 AND gender = 'Male').

Often though, providers have problems with InvocationExpressions, because of the obvious complications of processing a 'nested expression-call inside an expression.'

To get around this, LINQKit also provides the Expand helper. This essentially 'inlines' the invocation call smartly by replacing the call with the body of the nested expression, substituting uses of the nested expression's parameters appropriately (in this case, replacing p2 with p1). This should produce something like:

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

Note that this how you would have manually combined those predicates if you'd done it yourself in a lambda. But with LINQKit around, you can get these predicates from independent sources and easily combine them:

  1. Without writing "by hand" expression code.
  2. Optionally, in a way that is transparent to consumers of the resulting lambda.
Salley answered 15/7, 2012 at 12:24 Comment(6)
If LINQKit is capable of converting the InvocationExpression into a 'inline' expression, why not always do it to make the class more usable?Pottage
@just.another.programmer: That sounds like a question for the author of LINQKit .:) But here are some speculations: 1. And and Or were around from the first release, but Expand was added later (that's a fact), and the author didn't want to break compatibility. 2. To allow you to differentiate the "parts" out if you wanted to. 3. To not make you pay for a feature you don't need (many LINQ providers can handle invocation-expressions just fine).Salley
By pay I assume you mean in performance (LINQKit is free). Is there a serious performance hit with using the Expand method? Any ideas how it's implemented?Pottage
@just.another.programmer: Correct. Note that those reasons were just speculations on my part. To answer your last point, it's implemented by using an expression-visitor that walks the expression body, replacing any parameter expressions it finds along the way appropriately. The .NET framework subsequently released a public expression-visitor of its own: msdn.microsoft.com/en-us/library/…Salley
Hello @Ani, could you please explain more about the 2nd (about Invoke)? I still have not understood yet. How could the expression ` Invoke(p2 => p2.Gender == "Male", p1)` evaluated?Luisluisa
@DatVM: The framework / PredicateBuilder doesn't evaluate it - it just creates a tree with that node - it's up to the consumer of the tree (typically a LINQ provider) to do something with it.Salley

© 2022 - 2024 — McMap. All rights reserved.