LINQ: Not Any vs All Don't

311

经常情况下,我想要检查一个给定的值是否与列表中的某个值匹配(例如在验证时):

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

最近,我注意到ReSharper让我简化这些查询为:

if (acceptedValues.All(v => v != someValue))
{
    // exception logic
}

显然,这在逻辑上是相同的,也许稍微更易读(如果你做过很多数学),我的问题是:这样做会导致性能下降吗?

感觉应该会(即 .Any() 听起来像是短路操作,而 .All() 听起来不是),但我没有证据证明这一点。有人对查询是否会解析为相同的结果,或者ReSharper是否在误导我有更深入的了解吗?


6
你尝试过反汇编Linq代码以查看其操作吗? - RQDQ
9
在这种情况下,我实际上会选择if(!acceptedValues.Contains(someValue)),但当然这不是问题 :) 翻译:在这种情况下,我实际上会选择 if(!acceptedValues.Contains(someValue)) ,但是当然这并不是问题的核心。 - csgero
2
@csgero 我同意。上面的内容是对真实逻辑的简化(可能过于简化) 。 - Mark
1
“感觉应该是这样的(即 .Any() 听起来像会短路,而 .All() 听起来则不会)” - 不适用于任何具备良好直觉的人。你所指出的逻辑等价性意味着它们同样能进行短路。稍作思考就可以发现,只要遇到一个不符合条件的情况,All 就可以立即退出。 - Jim Balter
4
我不完全同意ReSharper的观点。要写出有意义的思路。如果您想在缺少必需的项目时引发异常:if (!sequence.Any(v => v == true))。如果您希望仅在所有内容符合某种规范时继续进行:if (sequence.All(v => v < 10)) - Timo
显示剩余2条评论
8个回答

375

根据ILSpy的实现(我确实去查看了,而不是像讨论理论而不是影响时可能会做的那样“好吧,那种方法有点像...")实现All

public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    if (predicate == null)
    {
        throw Error.ArgumentNull("predicate");
    }
    foreach (TSource current in source)
    {
        if (!predicate(current))
        {
            return false;
        }
    }
    return true;
}

根据ILSpy实现Any:

public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    if (predicate == null)
    {
        throw Error.ArgumentNull("predicate");
    }
    foreach (TSource current in source)
    {
        if (predicate(current))
        {
            return true;
        }
    }
    return false;
}
当然,生成的IL代码可能存在一些微妙的差异。但实际上并没有太大区别,除了在谓词匹配时返回true和在谓词不匹配时返回false这个明显的反转以外。当然,这只适用于linq-for-objects。其他linq提供程序有可能会对其中一个进行更好的处理,但如果是这种情况,哪个更优秀就基本上是随机的。似乎这条规则完全取决于某个人认为if(determineSomethingTrue)比if(!determineSomethingFalse)更简单、更易读。公平地说,我认为他们有点道理,因为当存在一个相同复杂度且能返回我们想要条件的真值测试的可替代测试时,我经常会发现if(!someTest)令人困惑。然而,在你给出的两个选择中,我个人并没有偏向任何一种,如果谓词更复杂,我可能会略微偏向前者。

9
我不确定在幕后做了什么,但对我来说更易读的是:if (not any) 而不是 if (all not equal)。 - VikciaR
62
当枚举没有值时,会有很大的区别。'Any' 始终返回 FALSE,而 'All' 始终返回 TRUE。因此,说一个是另一个的逻辑等价物并不完全正确! - Arnaud
53
@Arnaud Any 将返回 false,因此 !Any 将返回 true,因此它们是相同的。 - Jon Hanna
14
没有任何一个评论者说过Any和All在逻辑上是等价的,换句话说,所有评论者都没有说过Any和All在逻辑上是等价的。等价关系在于!Any(谓词)和All(!谓词)之间。 - Jim Balter
9
@MacsDickinson 这根本不是一个区别,因为你没有比较相反的谓词。等价于使用 All!test.Any(x => x.Key == 3 && x.Value == 1)test.All(x => !(x.Key == 3 && x.Value == 1))(这确实等价于 test.All(x => x.Key != 3 || x.Value != 1))。 - Jon Hanna
显示剩余6条评论

68
你可能会发现这些扩展方法可以使你的代码更易读:
public static bool None<TSource>(this IEnumerable<TSource> source)
{
    return !source.Any();
}

public static bool None<TSource>(this IEnumerable<TSource> source, 
                                 Func<TSource, bool> predicate)
{
    return !source.Any(predicate);
}

现在,不再使用您原来的代码

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

你可以说

if (acceptedValues.None(v => v == someValue))
{
    // exception logic
}

6
谢谢 - 我已经在考虑将这些东西实现到我们的公共库中,但我还没有决定是否这是一个好主意。我同意它们可以使代码更易读,但我担心它们并没有添加足够的价值。 - Mark
3
我寻找了“None”但没有找到。这样更易读懂。 - Rhyous
我不得不添加空值检查:return source == null || !source.Any(predicate); - Rhyous

32

两者的性能是相同的,因为它们都在可以确定结果后停止枚举 - 在第一个项目上使用 Any() 时传递的谓词计算结果为 true,并且在第一个项目上使用 All() 时传递的谓词计算结果为 false


21

All会在第一个非匹配处短路,所以这不是问题。

一个微妙之处在于

 bool allEven = Enumerable.Empty<int>().All(i => i % 2 == 0); 

是真的。序列中所有项都是偶数。

要了解更多关于此方法的信息,请查阅 Enumerable.All 的文档。


13
是的,但 bool allEven = !Enumerable.Empty<int>().Any(i => i % 2 != 0) 也是正确的。 - Jon Hanna
1
@Jon 从语义上讲,none != all。因此,从语义上讲,你要么有 none,要么有 all,但是在 .All() 的情况下,none 只是返回 true 的所有集合的子集,如果你不知道这个差异,就会导致 bug。对此给予 Anthony 一加。 - Rune FS
现在,确实会出现错误,因为假设“所有项都…”意味着至少有一项满足测试条件,因为对于空集,“所有项…”始终为真,我完全不否认这一点。但我补充说,如果假设“没有任何项…”意味着至少有一个项目不满足测试条件,那么同样的问题也可能发生,因为“没有任何项…”对于空集也总是为真。我的观点并不是与安东尼的观点相悖,而是我认为这也适用于讨论中的另一个构造。 - Jon Hanna
@Jon,你在谈逻辑,而我在谈语言学。人类大脑无法处理负面信息(在处理正面信息之前,它可以将其否定),因此在这个意义上两者之间存在相当大的差异。但这并不意味着你提出的逻辑是错误的。 - Rune FS
@RuneFS 我同意你的观点,百分之百(好吧,98%因为人脑在某些层面上可以处理否定,否则我们就无法进行这次对话了)。我的观点不是这两个结构在逻辑上是相同的(尽管这是*我的观点的一部分),而是如果有人未能理解第一个结构在空集方面的含义,那么他们同样可能会以同样的方式理解第二个结构。 - Jon Hanna
显示剩余3条评论

10

其他回答已经很好地涵盖了:这不是关于性能,而是关于清晰度。

对于你的两个选项,都有广泛的支持:

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

if (acceptedValues.All(v => v != someValue))
{
    // exception logic
}

但是我认为这可能会获得更广泛的支持:

var isValueAccepted = acceptedValues.Any(v => v == someValue);
if (!isValueAccepted)
{
    // exception logic
}

在否定任何事情之前,简单计算布尔值(并命名它)可以在我脑海中澄清很多问题。


我非常支持在评估条件时使用良好命名的“解释变量”,并经常在我的代码中使用它们,但有时在代码审查中仍会遇到阻力,因为它会增加更多“不必要”的行数。然而,在您的情况下,我建议否定解释变量,这样您就可以阅读if (valueIsNotAccepted) { ... }以消除括号中的!否定。 - Dib

10
如果你浏览一下Enumerable源代码,你会发现AnyAll的实现方法非常接近:
public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
    if (source == null) throw Error.ArgumentNull("source");
    if (predicate == null) throw Error.ArgumentNull("predicate");
    foreach (TSource element in source) {
        if (predicate(element)) return true;
    }
    return false;
}

public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
    if (source == null) throw Error.ArgumentNull("source");
    if (predicate == null) throw Error.ArgumentNull("predicate");
    foreach (TSource element in source) {
        if (!predicate(element)) return false;
    }
    return true;
}

两种方法之间不可能有显著的速度差异,因为唯一的区别在于布尔取反,所以应该优先考虑可读性而不是虚假的性能提升。


9

All()确定序列中的所有元素是否满足条件。
Any()确定序列中是否有任何元素满足条件。

Any()确定序列中是否有任何元素满足条件。

var numbers = new[]{1,2,3};

numbers.All(n => n % 2 == 0); // returns false
numbers.Any(n => n % 2 == 0); // returns true

6
根据这个链接

Any - 检查至少一个匹配项

All - 检查全部匹配项


3
你说得对,但是对于给定的集合,它们同时停止。当条件失败时,所有中断都会停止,而任何中断都会在匹配谓词时停止。因此,在技术上没有不同,除了在场景上可能有所不同。 - WPFKK

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