在lambda表达式中否定Func<T, bool>

30
Func<T, bool> expr = x => x.Prop != 1;

somelist = somelist.Where(expr);

到目前为止,一切都很好。但我想要像这样否定expr
somelist = somelist.Where(!expr);

这导致编译错误:无法将!运算符应用于类型为Func<T, bool>的操作数

我需要为此创建另一个表达式变量吗?

Func<T, bool> expr2 = x => x.Prop == 1;
2个回答

62
Func<T, bool> expr = x => x.Prop != 1;

Func<T, bool> negativeExpr = value => !expr(value);

或者

somelist = somelist.Where(value => !expr(value));

使用表达式树时,以下方法可以解决问题:
Expression<Func<T, bool>> expr = x => x.Prop != 1;

var negativeExpr = Expression.Lambda<Func<T, bool>>(
    Expression.Not(expr.Body), 
    expr.Parameters);

somelist = somelist.Where(negativeExpr);

为了让您的生活更加简单,您可以创建以下扩展方法:
public static Func<T, bool> Not<T>(
    this Func<T, bool> predicate)
{
    return value => !predicate(value);
}

public static Expression<Func<T, bool>> Not<T>(
    this Expression<Func<T, bool>> expr)
{
    return Expression.Lambda<Func<T, bool>>(
        Expression.Not(expr.Body), 
        expr.Parameters);
}

现在你可以这样做:

somelist = somelist.Where(expr.Not());

18

我只是随便说一个愚蠢的答案。请明确一点:我不会这样做,也不建议任何人这样做。 :)

我想知道是否有可能使用somelist.Where(!expr)语法或类似语法。

嗯,我成功了,但我讨厌自己。

var expr = N.egatable<MyClass>(x => x.Prop != 1);
somelist = someList.Where(!expr);

N.egatable只是一个小型的语法辅助工具,大多数情况下并不必要(编辑:我希望避免明确定义MyClass或以某种方式使对象包装器的实例化隐藏起来,但并未成功,想着也许有更好的方法):

public static class N
{
    public static Negator<T> egatable<T>(Func<T, bool> underlyingFunction)
    {
        return new Negator<T>(underlyingFunction);
    }
}

Negator<T> 是真正的“魔法”所在:

public class Negator<T>
{
    private Func<T, bool> UnderlyingFunction;

    public Negator(Func<T, bool> underlyingFunction)
    {
        this.UnderlyingFunction = underlyingFunction;
    }

    public static implicit operator Func<T, bool>(Negator<T> neg)
    {
        return v => neg.UnderlyingFunction(v);
    }

    public static Negator<T> operator !(Negator<T> neg)
    {
        return new Negator<T>(v => !neg.UnderlyingFunction(v));
    }
}

首先,! 运算符重载执行取反功能(就像这个答案中所述),然后隐式转换运算符到 Func<T, bool>,使其可以传递到 Where 扩展方法中。

也许很傻,但您可以像这样不断地翻转它:

somelist = someList.Where(!!expr);
somelist = someList.Where(!!!expr);
somelist = someList.Where(!!!!expr);
somelist = someList.Where(!!!!!expr);
somelist = someList.Where(!!!!!!expr); //oh my what

所以再次强调,请不要这样做。 :) 一定要遵循像Steven的回答中所述的正确/合理的做法。
编辑:以下是使用表达式实现的代码,语法使用方式完全相同。不确定它是否“正确”,也没有对Entity Framework进行测试。
public class ExpressionNegator<T>
{
    private Expression<Func<T, bool>> UnderlyingExpression;

    public ExpressionNegator(Expression<Func<T, bool>> underlyingExpression)
    {
        this.UnderlyingExpression = underlyingExpression;
    }

    public static implicit operator Func<T, bool>(ExpressionNegator<T> neg)
    {
        return neg.UnderlyingExpression.Compile();
    }

    public static implicit operator Expression<Func<T, bool>>(ExpressionNegator<T> neg)
    {
        return neg.UnderlyingExpression;
    }

    public static ExpressionNegator<T> operator !(ExpressionNegator<T> neg)
    {
        var originalExpression = neg.UnderlyingExpression;
        Expression<Func<T, bool>> negatedExpression = originalExpression.Update(
            Expression.Not(originalExpression.Body), 
            originalExpression.Parameters);
        return new ExpressionNegator<T>(negatedExpression);
    }
}

很抱歉让你受苦了,因为我知道这件事情可能会一直困扰着你,直到你解决它(我也曾经历过这种情况)。我想知道你是否可以使用 Expression<Func<T, bool>> 使其与 Linq2Entities 提供程序(如 Entity Framework)一起工作。 - Scott Chamberlain
1
@ScottChamberlain:我可能可以用表达式来做到这一点,但我不知道它是否能够转换为与实体兼容的运行时执行(我想它可能会,毕竟,在SQL查询中加入几个额外的否定又有什么关系呢?)。也许在我闲暇的时间里,我会试一试。 - Chris Sinclair
@ScottChamberlain:我添加了一个包含表达式的天真实现。我已经在本地列表上进行了测试,但还没有使用Entity Framework。请随意尝试并告诉我们它是否有效!Jean Hominal,感谢您提供的示例代码。 - Chris Sinclair
这里有另一个更简单的想法:创建一个“Not”扩展方法。例如:public static Func<T, bool> Not(this Func<T, bool> pr) { return v => !pr(v); }。现在你可以这样做:someList.Where(expr.Not())someList.Where(expr.Not().Not())等等。 - Steven
2
@ChrisSinclair:我确实欣赏你的努力,也喜欢人们在编程语言中做出疯狂的技巧。尤其是当这些代码在生产环境中运行时,我更是喜欢它!(开玩笑):P - Steven
显示剩余2条评论

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