为啥 Enumerable.All 对空序列返回 true? [复制]

Posted

技术标签:

【中文标题】为啥 Enumerable.All 对空序列返回 true? [复制]【英文标题】:Why does Enumerable.All return true for an empty sequence? [duplicate]为什么 Enumerable.All 对空序列返回 true? [复制] 【发布时间】:2011-12-14 15:42:17 【问题描述】:
var strs = new Collection<string>();
bool b = strs.All(str => str == "ABC");

代码创建一个空字符串集合,然后尝试确定集合中的所有元素是否都是“ABC”。 如果你运行它,b 将是真的。

但是该集合中甚至没有任何元素,更不用说任何等于“ABC”的元素了。

这是一个错误,还是有合理的解释?

【问题讨论】:

对我来说听起来很合理。结合使用Any()方法返回false。 这是一个有趣的数理逻辑问题。您可能想考虑相关问题“空整数序列的总和是多少?” (这似乎很明显)和“整数的整数序列的 product 是什么?” (较少)。 任何关于无所事事的说法总是正确的。如果一个孩子的盘子里从来没有蔬菜,然后说“我把所有的蔬菜都吃光了”,这将是一个真实的陈述,即使他一个都没吃。另一种说法是“在我盘子里的所有蔬菜中,我吃了它们。”这是一个真实的陈述,因为它是一个没有任何实际主张的空洞陈述,因为它适用于任何事物。 “对于我盘子里的每一种蔬菜,我都做了一个跳跃式千斤顶。”这并没有断言你做了任何跳跃式千斤顶,它断言“对于每种蔬菜”你做了一个跳跃式千斤顶。所以如果没有蔬菜... 有趣的是,这个问题的赞成票数是已接受答案的两倍,赞成票数是已接受答案的 3 倍,并且围绕它进行了更多讨论。这表明问题的这种表现形式最终比它重复的表现形式具有更大的价值,这表明封闭式问题仅对特权用户可见的政策存在缺陷。 【参考方案1】:

它是true,因为没有(无条件)使它成为false

文档可能会解释它。 (Jon Skeet 几年前也提到过一些事情)

Any(与 All 相反)也是如此,为空集返回 false

编辑:

您可以想象All 在语义上的实现与:

foreach (var e in elems)

  if (!cond(e))
    return false;

return true; // no escape from loop

【讨论】:

【参考方案2】:

该方法循环遍历所有元素,直到找到不满足条件的元素,或者找不到失败的元素。如果没有失败,则返回 true。

因此,如果没有元素,则返回 true(因为没有失败的元素)

【讨论】:

【参考方案3】:

All 要求谓词对于序列的所有元素都为真。这在文档中明确说明。如果您将All 视为每个元素的谓词结果之间的逻辑“与”,这也是唯一有意义的事情。 true 您正在为空序列退出是“与”操作的标识元素。同样,您从 Any 获得的空序列的 false 是逻辑“或”的标识。

如果您将All 视为“序列中没有不存在的元素”,这可能更有意义。

【讨论】:

"序列中没有不存在的元素",这个不错 这是对谓词逻辑的直观解释。 for all X, P(X)exists X, ~P(X) are logically equivalent. 这是迄今为止最好的答案。在设计这些方法时,考虑了数学背景。感谢您指出这一点,而不仅仅是在其他答案中一遍又一遍地说明“它写在文档中”!【参考方案4】:

将实施搁置一旁。如果它是真的,这真的很重要吗?看看你是否有一些代码迭代可枚举并执行一些代码。如果 All() 为真,那么该代码仍然不会运行,因为可枚举中没有任何元素。

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.

【讨论】:

是的,它有时真的很重要。例如,如果您想喂 .First() 狗。【参考方案5】:

这当然不是错误。它的行为与 as documented 完全相同:

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

现在您可以争论它是否应该以这种方式工作(对我来说这似乎很好;序列的每个元素都符合谓词)但是首先要检查 在你问某事是否是错误之前,是文档。 (当方法的行为与您的预期不同时,首先要检查。)

【讨论】:

如果没有此属性,!Any(Predicate) 将与 All(!Predicate) 不同,这将是非常不直观的。 根据另一个发帖者我发现这种情况叫做vacuous truth 实际上,搜索中的顶部 SO 链接是我在函数未按预期运行时检查的第一件事。总是有人像 Jon Skeet 这样享有盛誉的人不仅找到了相关的文档 sn-p,而且还解释了为什么我的期望一开始就错了。我们被这样宠坏了…… 我并不是说这是一个错误,但它很奇怪。我的任何银行账户都没有钱,所以 BankAccounts.All(x=>x.TotalFunds > 1000000) 是真的???我要回家了! @Stimul8d:只有当你根本没有任何银行账户(或者如果你有银行账户,并且所有这些账户都拥有这么多银行账户时,这才是正确的)。【参考方案6】:

这里的大多数答案似乎都遵循“因为这是定义的方式”。但是这样定义也有一个合乎逻辑的原因。

定义函数时,您希望函数尽可能通用,以便可以应用于尽可能多的情况。例如,假设我想定义 Sum 函数,它返回列表中所有数字的总和。当列表为空时它应该返回什么?如果您要返回任意数字x,您可以将函数定义为:

    返回给定列表中所有数字之和的函数,如果列表为空,则返回 x

但如果x为零,你也可以将其定义为

    返回x 加上给定数字的函数。

请注意,定义 2 意味着定义 1,但当 x 不为零时,1 并不意味着 2,这本身就是选择 2 而不是 1 的充分理由。但是注意 2 更优雅 并且,就其本身而言,比 1 更通用。就像将聚光灯放在更远的地方,以便照亮更大的区域。实际上要大很多。我自己不是数学家,但我相信他们会在定义 2 和其他数学概念之间找到大量联系,但当 x 不为零时,与定义 1 相关的联系并不多。

一般来说,只要您有一个对一组元素应用二元运算符且该集合为空的函数,您就可以并且很可能希望返回identity element(保持另一个操作数不变的那个)。这与Product 函数在列表为空时返回 1 的原因相同(请注意,您可以在定义 2 中将“x plus”替换为“one times”)。与All(可以认为是逻辑与运算符的重复应用)在列表为空时返回true的原因相同(p &amp;&amp; true等价于p),同样的原因Any(OR 运算符)将返回 false

【讨论】:

确实如此。从数学上讲,“and”是乘法:“a and b”等价于“a * b”。通过这种方式,您可以证明 And() 行为与例如零幂法则。【参考方案7】:

这是一个可以做 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:如果寻找默认值本身,上面的不起作用! (对于字符串来说,这将为空。) 在这种情况下,它会变得不那么优雅,类似于:

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

如果可以多次列举,最简单的解决方案是:

strs.All(predicate) && strs.Any();

即在实际存在 any 元素之后添加一个检查。

【讨论】:

以上是关于为啥 Enumerable.All 对空序列返回 true? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

防止在 DataContractSerializer 中对空成员进行序列化

如果数字为负,为啥在递归解决方案中找到给定序列中的最大子序列的基本情况返回 0?

为啥我会收到“错误:序列化从 getStaticProps 返回的 ___ 时出错”?

对空向量进行 std::sort [关闭]

Lua 对空 FIFO 的非阻塞读访问

如何对空的Optional执行空检查