为什么Enumerable.All对于一个空序列返回true?

138
var strs = new Collection<string>();
bool b = strs.All(str => str == "ABC");
代码创建了一个空字符串集合,然后试图确定集合中的所有元素是否都为"ABC"。如果运行它,b将会是true。
但是这个集合甚至没有任何元素,更不用说有任何等于"ABC"的元素了。
这是一个bug吗?还是有一个合理的解释?

4
我认为这听起来很合理。与其一起使用Any()方法来返回false。 - Yuriy Faktorovich
3
这是一个有趣的数理逻辑问题。或许你会想到相关的问题:「空整数序列的总和是多少?」(看起来很显然)以及「空整数序列的积是多少?」(不太好想)。 - AakashM
14
关于“全无之物”,你说的任何话都是正确的。如果一个孩子从来没有在他的盘子里吃过任何蔬菜,但却说:“我吃完了我的蔬菜”,那将是一个真实的陈述,即使他没有吃任何东西。另一种说法是“在我的盘子里所有的蔬菜中,我把它们都吃掉了。”这是一个真实的陈述,因为它是空语句,没有任何实际断言,因为它适用于不存在的事物。“对于盘子里每个蔬菜,我都做了一个弹跳动作。”这并不表示你已经做了任何跳跃动作,而是断言“对于每个蔬菜”你都做了一个弹跳动作。所以如果没有蔬菜... - Triynko
3
有趣的是,这个问题的点赞数是原问题的两倍,比被采纳答案的点赞数多3倍,并且关于它的讨论也更加广泛。这表明,这个问题的呈现形式比重复的那个问题更有价值,这意味着关闭问题只对特权用户可见的政策存在缺陷。 - Derek Greer
2
@Triynko,这个直观的问题就像“如果你盘子里所有的蔬菜都是绿色的,那么跳个仰卧起坐”。如果我告诉你这个问题,但是你的盘子是空的,你会跳仰卧起坐吗?我不会。如果我的盘子里没有蔬菜,那么我的盘子上就没有绿色的蔬菜。 - xr280xr
7个回答

200

这显然不是一个漏洞。它的行为恰恰符合文档所述:

如果源序列的每个元素都通过指定谓词中的测试,或者序列为空,则为 true;否则为 false。

现在你可以争论它是否应该这样工作(我觉得很好;序列的每个元素都符合谓词),但在询问某个东西是否有漏洞之前,第一件要检查的事情是文档。(当一个方法的行为与你预期的不同时,这是要检查的第一件事情。)


68
如果没有这个属性,!Any(Predicate)将不会与All(!Predicate)相同,这将会非常令人费解。 - Ani
36
根据另一位发帖人的说法,我发现这种情况被称为“空虚真理”(vacuous truth)。 - Paul Tyng
11
实际上,在函数表现不符合预期时,我首先查看搜索结果中排名最高的 SO 链接。像 Jon Skeet 这样声望很高的人不仅能找到相关的文档片段,还能解释为什么我的期望是错误的。我们被 SO 宠坏了... - ThisGuy
12
我并不是说这是个错误,但它很奇怪。我所有的银行账户都没有钱,但BankAccounts.All(x=>x.TotalFunds > 1000000)却是真的???我要回家了! - EightyOne Unite
12
只有当您根本没有银行账户(或者如果您银行账户,而且所有账户里都有那么多钱)时,这才是真的。 - Jon Skeet
显示剩余16条评论

22

All需要对序列中的所有元素都满足谓词条件。这在文档中明确说明了。如果你把All看作是在每个元素的谓词结果之间执行逻辑“与”操作,那么只有满足所有元素才有意义。对于空序列返回的true是逻辑“与”操作的恒等元素,而Any对于空序列返回的false是逻辑“或”的恒等元素。

如果你把All看作是“序列中没有不满足条件的元素”,可能会更容易理解。


8
这句话的意思是“序列中没有元素不是...”,这是一个好的表述。 - Cui Pengfei 崔鹏飞
这是在谓词逻辑中直观的解释。对于所有X,P(X)存在X,~P(X) [在逻辑上等价](http://en.wikipedia.org/wiki/Universal_quantification#Negation)。 - Brian
2
这绝对是最好的答案。在设计这些方法时,数学背景得到了考虑。感谢您指出这一点,而不像其他答案那样一遍又一遍地重复“文档中写着”! - florien

11

如果没有任何条件可以使其为false,那么它就是true

文档可能已经解释了这点。 (Jon Skeet几年前也提到过一些东西)

Any(与All相反)对于空集返回false,同样适用。

编辑:

您可以想象All的语义实现方式与以下代码相同:

foreach (var e in elems)
{
  if (!cond(e))
    return false;
}
return true; // no escape from loop

8
大多数答案的观点都是“因为这样定义”。但是也有逻辑上的原因来解释为什么要这样定义。在定义函数时,您希望函数尽可能通用,以便可以应用于尽可能多的情况。例如,假设我想定义返回列表中所有数字之和的Sum函数。当列表为空时,应该返回什么?如果返回任意数字x,则将函数定义为:
1. 返回给定列表中所有数字的总和或x(当列表为空时)。
但是,如果将x定义为零,则还可以将其定义为:
2. 返回x加上给定数字的函数。
请注意,定义2暗示定义1,但是当x不为零时,1不暗示2,这本身就足以选择2而不是1。但是请注意,2更加优雅且更通用。就好像将聚光灯放得更远,以照亮更大的区域一样。实际上要大得多。我自己不是数学家,但我确定他们会在定义2和其他数学概念之间找到大量联系,但与当x不为零时的定义1相关的联系会较少。
通常情况下,当您有一个应用于元素集合的二元运算符的函数,并且该集合为空时,您可以(并且最有可能)返回恒等元素(将另一个操作数保持不变的元素)。这也是当列表为空时Product函数将返回1的原因(请注意,您可以在定义2中用“一次乘以”替换“x加”)。并且也是All(可以被视为逻辑AND运算符的重复应用)在列表为空时将返回true的原因(p && true等价于p),以及Any(OR运算符)将返回false的原因。

确实。从数学角度来看,“and”就是乘法:“a and b”等同于“a * b”。通过这种方式,您可以使用零次幂规则来证明And()的行为。 - Fax
远远好于那些只是简单回答:“就是这样因为就是这样”的答案。非常感谢。 - undefined

4
该方法遍历所有元素,直到找到一个不满足条件的元素,或者没有找到任何失败的元素。如果没有失败的元素,则返回 true。
因此,如果没有元素,则返回 true(因为没有任何元素失败)。

3

这里有一个扩展程序可以实现OP想要做的事情:

static bool All<T>(this IEnumerable<T> source, Func<T, bool> predicate, bool mustExist)
{
    foreach (var e in source)
    {
        if (!predicate(e))
            return false;
        mustExist = false;
    }
    return !mustExist;
}

正如其他人已经指出的那样,这不是一个错误,而是经过充分记录的预期行为。

如果不想编写新扩展程序,则可以采用另一种解决方案:

strs.DefaultIfEmpty().All(str => str == "ABC");

PS:如果要查找默认值本身,则上述方法无效!(对于字符串,它是null。)在这种情况下,可以使用类似以下的代码:

strs.DefaultIfEmpty(string.Empty).All(str => str == null);

如果您可以多次枚举最简单的解决方案,则是:
strs.All(predicate) && strs.Any();

也就是说,在检查是否存在任何元素之后,只需添加一个检查。


1

把实现放在一边,这是否真的很重要?如果您有一些代码遍历可枚举并执行某些代码,则如果 All() 为 true,则该代码仍然不会运行,因为可枚举没有任何元素。

var hungryDogs = Enumerable.Empty<Dog>();
bool allAreHungry = hungryDogs.All(d=>d.Hungry);    
if (allAreHungry)
    foreach (Dog dog in hungryDogs)
         dog.Feed(biscuits); <--- this line will not run anyway.

6
有时候确实会有影响。比如说,如果你想喂第一个狗狗,就需要使用.First() - Kenneth_hj
如果您想执行其他操作,也可以。引发我提出这个问题的原因是我们有一个移动聊天应用程序,需要知道是否有任何客服代表可供聊天。如果没有客服代表,我们希望将用户重定向到发送电子邮件的页面。 - B2K

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