PredicateBuilder是如何工作的?

38
《C#权威指南》中有一个名为 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);
}

2
你不明白什么?这个函数有几个部分,我想了解你到底哪些部分让你感到困惑。 - Oded
1
什么是invokedExpression?当前表达式(expr1)的参数如何用于生成该invokedExpression?为什么Lambda调用使用expr1.Body再次组合这两个表达式? - just.another.programmer
11
PredicateBuilder 的不是 "C# in Depth",而是 "C# in a Nutshell"。 - Jon Skeet
@JonSkeet,我刚意识到这其中的讽刺意味。谢谢你让我笑了。 - just.another.programmer
1个回答

62

假设你有以下内容:

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. 生成的Lambda表达式会重用第一个表达式的参数。
  2. 它有一个体,该体调用第二个表达式,通过使用第一个表达式的参数作为第二个表达式参数的替换来进行调用。生成的 InvocationExpression 类似于方法调用的表达式等效形式(通过传入参数进行参数传递调用程序)。
  3. And 连接的是第一个表达式的主体和这个 InvocationExpression ,以生成Lambda的主体。

这里的思路是LINQ提供程序应该能够理解这个操作的语义,并采取合适的行动(例如生成SQL,例如 WHERE age >= 18 AND gender = 'Male')。

不过经常会遇到提供程序因为处理"嵌套在表达式内部的表达式调用"而出现问题的情况,为了解决这个问题,LINQKit还提供了 Expand 助手。它通过智能地将调用替换为嵌套表达式的主体并适当地替换嵌套表达式的参数使用(在本例中,用 p1 替换 p2 ),从而内联调用。这应该会产生类似于:

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

请注意,这就是您如果使用lambda手动合并这些谓词的方法。但是有了LINQKit,您可以从独立源获取这些谓词,并轻松地组合它们:

  1. 无需编写“手工”表达式代码。
  2. 可选择以对生成的lambda的消费者透明的方式进行。

2
如果LINQKit能够将InvocationExpression转换为“内联”表达式,为什么不总是这样做以使类更易用? - just.another.programmer
@just.another.programmer:这听起来像是一个问 LINQKit 作者的问题。:) 不过这里有一些猜测:1. AndOr 从第一个版本就存在了,但 Expand 是后来添加的(这是事实),作者不想破坏兼容性。2. 如果你想要区分“部分”,可以使用它。3. 不让你为你不需要的功能付费(许多 LINQ 提供程序都可以很好地处理调用表达式)。 - Ani
通过“支付”,我假设您指的是性能(LINQKit是免费的)。使用Expand方法会导致严重的性能损失吗?有什么关于它实现的想法吗? - just.another.programmer
1
@just.another.programmer:没错。请注意,那些原因只是我个人的猜测。回答你最后一个问题,它是通过使用表达式访问器来实现的,该访问器遍历表达式主体,在适当的位置替换任何参数表达式。随后,.NET框架发布了自己的公共表达式访问器:http://msdn.microsoft.com/en-us/library/system.linq.expressions.expressionvisitor.aspx - Ani
你好@Ani,能否请您更详细地解释第二个问题(关于Invoke)?我还没有完全理解。表达式Invoke(p2 => p2.Gender == "Male", p1)是如何被评估的? - Luke Vo
4
PredicateBuilder框架本身不会对其进行求值,它只是创建一个包含该节点的树形结构。如何处理这个树形结构取决于使用该结构的消费者(通常是LINQ提供程序)。 - Ani

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接