Why does Enumerable.All return true for an empty sequence?

var strs = new Collection<string>();
bool b = strs.All(str => str == "ABC");

The code creates an empty collection of string, then tries to determine if all the elements in the collection are "ABC". If you run it, b will be true.

But the collection does not even have any elements in it, let alone any elements that equal to "ABC".

Is this a bug, or is there a reasonable explanation?

43863 次浏览

它是 true,因为没有任何条件使它成为 false

医生可能已经解释过了。(乔恩 · 斯基特几年前也提到过一些事情)

对于空集返回 falseAny(与 All相反)也是如此。

编辑:

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

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

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

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

All要求序列的所有元素的谓词都为 true。这在文档中有明确说明。如果您认为 All在每个元素的谓词结果之间像一个逻辑“ and”,那么这也是唯一有意义的事情。你得到的空序列的 true是“ and”操作的标识元素。同样,从 Any获得的空序列的 false是逻辑“或”的标识。

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

把实现放在一边。这是不是真的重要吗?看看是否有一些代码在可枚举数上迭代并执行一些代码。如果 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.

这当然不是一个错误。它的行为准确地 记录在案:

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

现在您可以讨论 应该是否按照这种方式工作(对我来说似乎没问题; 序列中的每个元素都符合谓词) ,但是在您询问某个东西是否是 bug 之前,第一件要检查的事是文档。(一旦某个方法的行为与您预期的不同,这是首先要检查的事情。)

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

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

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

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

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

请注意,定义2意味着定义1,但是当 x不为零时,定义1并不意味着2,这本身就足以说明选择2除以1的理由。但是也注意到2是 更优雅,并且,在它自己的权利中,比1更一般。就像把聚光灯放在更远的地方,照亮更大的区域。实际上要大得多。我自己不是一个数学家,但是我确信他们会发现定义2和其他数学概念之间有很多联系,但是当 x不是零的时候,与定义1相关的就没有那么多了。

通常,只要有一个函数在一组元素上应用二进制运算符,并且该集为空,就可以返回 身份元素(保持另一个操作数不变的操作数) ,而且最有可能返回这个操作数。这与当列表为空时 Product函数将返回1的原因相同(注意,在定义2中可以用“ one times”替换“ x plus”)。这与 All(可以认为是逻辑 AND 操作符的重复应用)在列表为空时返回 true的原因相同(p && true等价于 p) ,与 Any(OR 操作符)返回 false的原因相同。

下面是一个扩展,它可以完成 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();

也就是在实际上有 任何元素之后简单地添加一个检查。