将表达式与运算符&&结合

4

我有一个类,它的工作方式有点像Linq To Sql的Where子句。

它从表达式树中构建操作序列。

表达式树是一个Expression<Func<bool>>(即一个没有参数且返回布尔值的lambda表达式)。

conditionBuilder.BuildCondition(() => x != 3 && y != 5);

这个类对于像上面例子中的normal表达式可以正常工作,但现在我需要将多个表达式组合起来。

我已经添加了And、Or等方法,例如:

var exp1 = () => x != 3;
var exp2 = () => y != 5;
var exp = ConditionBuilder.And(exp1, exp2);

当组合多个表达式时,它会变得复杂。

我想这样写:

var exp = exp1 && exp2;

但由于我不能直接重载运算符&&,因此需要找到其他解决方案。棘手的部分是,位运算的结果操作没有布尔运算符重载。即exp1&exp2的结果是int而不是bool。(我可以通过添加!=0来解决这个问题)

所以我的问题现在是:

  • 如果我让运算符&成为逻辑表达式(即AndAlso),那么会不会令人困惑?
  • 如果我重载&&/true/false,则运算符&&将起作用,但这也将创建一个隐式布尔转换。我知道在C++中应该避免隐式布尔转换,但我不确定它在C#中有何影响。此外,覆盖的true和false是否应该评估表达式?(即if(exp1)应该做什么?)

编辑: 我已经有了这样的可工作代码:

public class ConditionBuilder
{
    private readonly Expression<Func<bool>> _filter;

    public ConditionBuilder(Expression<Func<bool>> filter) {
        _filter = filter;
    }

    public static ConditionBuilder And(ConditionBuilder left, ConditionBuilder right) {
        return new ConditionBuilder(Expression.Lambda<Func<bool>>(Expression.AndAlso(left._filter.Body, right._filter.Body)));
    }

    public static ConditionBuilder Or(ConditionBuilder left, ConditionBuilder right) {
        return new ConditionBuilder(Expression.Lambda<Func<bool>>(Expression.OrElse(left._filter.Body, right._filter.Body)));
    }
}

编辑2:澄清问题。

这些表达式会被转换成另一种格式。例如,() => ConditionBuilder.IntField(123) == 5 被转换为 @123 EQ 5(实际格式可能不同,但你可以理解)。

问题是另一种格式没有按位运算符的布尔重载。这意味着 () => true & false 被转换为 True BITAND False,这不是一个有效的表达式,因为它返回一个整数而不是布尔值。

如果我重载 & 来表示 AndAlso

exp1 & exp2

这是一个有效的表达式,但是

() => x != 3 & y != 5

不是。

我的第二个问题是,C#中是否存在像C++中一样的隐式转换到布尔类型会导致问题。


1
考虑到表达式库本身具有 AndOr 等功能,我会对选择运算符重载持谨慎态度,尽管其在使用时更加简洁。你提到“当组合多个表达式时会变得复杂”,但运算符重载的等效方法在创建和调试时肯定会更加困难,不是吗? - AakashM
1个回答

3
我会重载&运算符。这并不会引起困惑,因为&根据上下文可以是逻辑或位运算。
要重载&运算符,您需要使用一个包装类(例如ConditionBuilder)来重载您的运算符。
您可以在真正的运算符的文档中看到一个完整的示例,包括重载&运算符,链接为http://msdn.microsoft.com/en-us/library/6x6y6z4d.aspx
使用ConditionBuilder的简单示例,以演示如何重载&运算符。
void Main ()
{
    int x = 1;
    int y = 1;

    var exp1 = new ConditionBuilder (() => x != 3);
    var exp2 = new ConditionBuilder (() => y != 5);
    var exp3 = exp1 & exp2;

    Console.WriteLine (exp3.Execute ());
}

public class ConditionBuilder
{
    private readonly Expression<Func<bool>> _filter;

    public ConditionBuilder(Expression<Func<bool>> filter) {
        _filter = filter;
    }

    public bool Execute() {
        return _filter.Compile()();
    }

    public static ConditionBuilder And(ConditionBuilder left, ConditionBuilder right) {
        return new ConditionBuilder(Expression.Lambda<Func<bool>>(Expression.AndAlso(left._filter.Body, right._filter.Body)));
    }

    public static ConditionBuilder Or(ConditionBuilder left, ConditionBuilder right) {
        return new ConditionBuilder(Expression.Lambda<Func<bool>>(Expression.OrElse(left._filter.Body, right._filter.Body)));
    }

    public static ConditionBuilder operator & (ConditionBuilder left, ConditionBuilder right) {
        // Note this could confuse users for the problem discussed below.
        // Consider using Expression.And instead of AndAlso
        return ConditionBuilder.And(left, right);
    }

    public static ConditionBuilder operator | (ConditionBuilder left, ConditionBuilder right) {
        // Note this could confuse users for the problem discussed below.
        // Consider using Expression.Or instead of OrElse
        return ConditionBuilder.Or(left, right);
    }
}

当使用短路运算符 (AndAlso, OrElse) 与 &| 进行配对时要小心,例如给定 Foo() & Bar(),如果 Foo 返回 false,则大多数用户都不会预期 Bar 将 不会 被调用。


替代方案

将 ConditionBuilder 的 And/Or 方法更改为实例成员,并仅将右侧作为参数传递。

public ConditionBuilder And(ConditionBuilder right) {
    return new ConditionBuilder(Expression.Lambda<Func<bool>>(Expression.AndAlso(_filter.Body, right._filter.Body)));
}

这将产生类似于var exp3 = exp1.And(exp2);的语法,比原始语法更加整洁,并且能够合理地链接exp1.And(exp2).And(exp3)
在混合使用And/Or时,您需要特别小心,因为 exp1.Or(exp2).And(exp3) 会得到 (exp1 | exp2) & exp3 而不是 exp1 | (exp2 & exp3)。这可以通过显式括号来避免,例如:(exp1.Or(exp2)).And(exp3) vs exp1.Or(exp2.And(exp3))

&&支持存在的问题

要支持&&,您需要在ConditionBuilder中支持truefalse运算符,这意味着需要编译和执行表达式。
public static bool operator true (ConditionBuilder left) {
    return left.Execute();
}

public static bool operator false (ConditionBuilder left) {
    return !left.Execute();
}

当使用LinqPAD测试时,var exp3 = exp1 && exp2的结果并不理想。

  1. 必须立即执行exp1以解决短路问题。
  2. 当exp1为真时,得到的表达式等价于:
    var exp3 = () => x != 3 && y != 5
  3. 当exp1为假时,得到的表达式等价于:
    var exp3 = () => x != 3

那样做不行,因为它会创建一个 lambda 而不是表达式。请查看我的编辑。 - adrianm
抱歉,我只是使用 Func<bool> 是因为它足够简单,可以写一个简短的例子。你可以有任何返回值,例如 return ConditionBuilder.And(lhs, rhs) - Chris Chilvers
所以,我已经修复了答案。通过使用Linq Pad进行测试,似乎它实际上被编译为类似于if(false(lhs)) lhs else operator&(lhs, rhs)的东西。 - Chris Chilvers
我认为,如果你想让 && 的行为与 & 相同(即不进行短路),则不需要编译和执行 true()false() 中的表达式。 - svick
也许不是这样,你总可以返回true/false,但对我来说似乎有些奇怪,因为var expr = new ConditionBuilder(() => false); if (expr) Foo()会编译通过,但当用户可能希望不执行Foo时,它会执行。 - Chris Chilvers
显示剩余4条评论

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