我有一段时间有这个想法,我很好奇是否有可能以有价值的方式实施。我想获取一个布尔返回的lambda表达式数组,并对其结果执行逻辑运算。这是一个有效列表的无意义的例子:
var tests = new List<Func<int, bool>>() {
(x) => x > 10,
(x) => x < 100,
(x) => x != 42
};
我想做的事实上是
bool result = tests.And();
或执行其他逻辑操作。我意识到我可以写一个IEnumerable实现类并执行此操作,但我想知道它是否已经完成。显然,实现必须以与
相同的方式有效地工作,短路if (truthyExpression || falseyExpression)
永远无法评估falseyExpression
。
我在框架中看到的唯一可能有帮助的是Bit Array,但我不确定如何在不预先评估每个表达式的情况下使用它,从而破坏了短篇小说的有用性
答案 0 :(得分:5)
您可以使用内置的Enumerable.Any
和Enumerable.All
扩展程序。
bool andResult = tests.All(test => test(value));
bool orResult = tests.Any(test => test(value));
答案 1 :(得分:2)
当然可以。
var tests = new List<Func<int, bool>>() {
(x) => x > 10,
(x) => x < 100,
(x) => x != 42
};
我们将通过逐步逻辑地将所有这些谓词聚合为一个,并将每个谓词与已存在的结果进行汇总。既然我们需要从某个地方开始,我们将从x => true
开始,因为该谓词在执行AND时是中性的(如果你是OR,则从x => false
开始):
var seed = (Func<int, bool>)(x => true);
var allTogether = tests.Aggregate(
seed,
(combined, expr) => (Func<int, bool>)(x => combined(x) && expr(x)));
Console.WriteLine(allTogether.Invoke(30)); // True
这很简单!它确实有一些限制:
这可以在任何地方使用(例如,您也可以使用它将谓词传递给SQL提供程序,例如Entity Framework),并且在任何情况下它也会提供更“紧凑”的最终结果。但要让它发挥作用要困难得多。我们来吧。
首先,将输入更改为表达式树。这很简单,因为编译器会为您完成所有工作:
var tests = new List<Expression<Func<int, bool>>>() {
(x) => x > 10,
(x) => x < 100,
(x) => x != 42
};
然后将这些表达式的主体聚合成一个,与以前相同的想法。不幸的是,这不是微不足道的,不一直在工作,但请耐心等待:
var seed = (Expression<Func<int, bool>>)
Expression.Lambda(Expression.Constant(true),
Expression.Parameter(typeof(int), "x"));
var allTogether = tests.Aggregate(
seed,
(combined, expr) => (Expression<Func<int, bool>>)
Expression.Lambda(
Expression.And(combined.Body, expr.Body),
expr.Parameters
));
现在我们在这里做的是从所有单个谓词构建一个巨大的BinaryExpression
表达式。
您现在可以将结果传递给EF或告诉编译器将其转换为代码并运行它,并且您可以免费获得短路:
Console.WriteLine(allTogether.Compile().Invoke(30)); // should be "true"
不幸的是,最后一步不会因为深奥的技术原因而起作用。
但为什么它不起作用?
因为allTogether
表示的表达式树有点像这样:
FUNCTION
PARAMETERS: PARAM(x)
BODY: AND +-- NOT-EQUAL +---> PARAM(x)
| \---> CONSTANT(42)
|
AND +-- LESS-THAN +---> PARAM(x)
| \---> CONSTANT(100)
|
AND +-- GREATER-THAN +---> PARAM(x)
| \---> CONSTANT(10)
|
TRUE
上面树中的每个节点代表要编译的表达式树中的Expression
对象。问题是这些PARAM(x)
节点中的所有4个节点在逻辑上相同,实际上是不同的实例(这有助于编译器通过自动创建表达式树来提供给我们?好吧,每个节点自然都有它们自己的参数实例),而对于工作的最终结果,它们必须是同一个实例。我知道这是因为it has bitten me in the past。
所以,这里需要做的是,然后迭代生成的表达式树,找到ParameterExpression
的每个匹配项,并用相同的实例替换它们中的每一个。同一个实例也是构造seed
时使用的第二个参数。
显示如何做到这一点将使这个答案的方式比它有权利的时间更长,但无论如何我们都要这样做。我不打算发表评论,你应该认识到这里发生了什么:
class Visitor : ExpressionVisitor
{
private Expression param;
public Visitor(Expression param)
{
this.param = param;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return param;
}
}
然后:
var param = Expression.Parameter(typeof(int), "x");
var seed = (Expression<Func<int, bool>>)
Expression.Lambda(Expression.Constant(true),
param);
var visitor = new Visitor(param);
var allTogether = tests.Aggregate(
seed,
(combined, expr) => (Expression<Func<int, bool>>)
Expression.Lambda(
Expression.And(combined.Body, expr.Body),
param
),
lambda => (Expression<Func<int, bool>>)
// replacing all ParameterExpressions with same instance happens here
Expression.Lambda(visitor.Visit(lambda.Body), param)
);
Console.WriteLine(allTogether.Compile().Invoke(30)); // "True" -- works!
答案 2 :(得分:2)
为了将Func<int, bool>
个对象的序列转换为bool
,您需要有一个整数来应用于每个值。如果您已经知道该整数是什么,那么您可以执行Julien describes:
bool andResult = tests.All(test => test(value));
bool orResult = tests.Any(test => test(value));
如果你不这样做,那么你想要做的是从布尔序列中创建一个Func<int, bool>
,而不是bool
:
Func<int, bool> andResult = value => tests.All(test => test(value));
Func<int, bool> orResult = value => tests.Any(test => test(value));
我们可以很容易地将其概括为通用函数:
public static Func<T, bool> And<T>(this IEnumerable<Func<T, bool>> predicates)
{
return value => predicates.All(p => p(value));
}
public static Func<T, bool> Or<T>(this IEnumerable<Func<T, bool>> predicates)
{
return value => predicates.Any(p => p(value));
}
允许你写:
Func<int, bool> result = tests.And();
答案 3 :(得分:0)
这个怎么样?
using System;
using System.Collections.Generic;
using System.Linq;
namespace SO7
{
class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
LogicList<int> intBasedLogicalList = new LogicList<int>(new Func<int, bool>[] {x => x<3, x => x <5, x => x<8});
Console.WriteLine(intBasedLogicalList.And(2));
Console.WriteLine(intBasedLogicalList.And(4));
Console.WriteLine(intBasedLogicalList.Or(7));
Console.WriteLine(intBasedLogicalList.Or(8));
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}
}
public class LogicList<T> : List<Func<T, bool>>
{
private List<Func<T,bool>> _tests;
public LogicList(IEnumerable<Func<T, bool>> tests)
{
_tests = new List<Func<T, bool>>();
foreach(var test in tests)
{
_tests.Add(test);
}
}
public bool And(T argument){
foreach(var test in _tests)
{
if (!test(argument)){
return false;
}
}
return true;
}
public bool Or(T argument){
return _tests.Any(x => x(argument));
}
}
}