为什么IQueryable.All()在空集合上返回true?
所以今天遇到了一些情况,一些生产代码不能正确地执行,因为一个方法完全按照MSDN的规定执行 。 因为我没有阅读文档而感到羞耻。 然而,即使“devise上”,我仍然在为自己的行为而挠头,因为这种行为与我期望的(和其他已知的行为)完全相反,因此似乎违反了原则最less的惊喜。
All()
方法允许您提供谓词(如lambdaexpression式)来testingIQueryable
,并返回一个布尔值,指示所有集合成员是否匹配testing。 到现在为止还挺好。 这是奇怪的地方。 如果集合是空的, All()
也返回true
。 这似乎完全倒退给我,原因如下:
- 如果这个集合是空的,像这样的testing最好是未定义的。 如果我的车道是空的,我不能断言所有停在那里的车都是红色的。 有了这个行为,在一个空的车道上,所有停在那里的车都是红色的,蓝色的和棋盘格 – 所有这些expression式都会返回true。
- 对于熟悉SQL!的人来说,NULL!= NULL,这是意料之外的行为。
-
Any()
方法的行为与预期相同,并且(正确)返回false,因为它没有任何与谓词相匹配的成员。
所以我的问题是,为什么All()
这样做? 它解决了什么问题? 这是否违反了最小惊喜的原则?
我把这个问题标记为.NET 3.5,但是这个行为也适用于.NET 4.0。
编辑好的,所以我把握这个逻辑层面,就像杰森和你其他人一样。 诚然,一个空的收集是一个边缘的情况下。 我想我的问题是根植于这样一种斗争,即只是因为某些东西是合乎逻辑的 ,并不意味着如果你不在正确的思维范围内,这是有道理的。
如果我的车道是空的,我不能断言所有停在那里的车都是红色的。
考虑下面的陈述。
S1
:我的车道是空的。
S2
:停在我车道的车都是红色的。
我声称S1
意味着S2
。 就是说,语句S1 => S2
是真的。 我会通过表明它的否定是假的来做到这一点。 在这种情况下, S1 => S2
的否定是S1 ^ ~S2
; 这是因为S1 => S2
仅当S1
为真且S2
为假时才是错误的。 S2
的否定是什么? 它是
~S2
:车道上停放的车辆不是红色的。
S1 ^ ~S2
的真值是多less? 我们把它写出来
S1 ^ ~S2
:我的车道是空的,车道上停着一辆不是红色的车。
S1 ^ ~S2
可以是真的唯一方法是S1
和~S2
都是真的。 但S1
说我的车道是空的, S2
说在我的车道上有一辆车。 我的车道不能都是空的,并包含一辆车。 因此, S1
和~S2
都不可能是正确的。 因此, S1 ^ ~S2
是错误的,所以它的否定S1 => S2
是真的。
因此,如果你的车道是空的,你可以断言所有停在那里的车都是红色的。
所以现在让我们考虑一个IEnumerable<T> elements
和一个Predicate<T> p
。 让我们假设elements
是空的。 我们希望发现的价值
bool b = elements.All(x => p(x));
让我们考虑它的否定
bool notb = elements.Any(x => !p(x));
对于notb
是真实的,在!p(x)
为真的elements
必须至less有一个x
。 但是elements
是空的,所以不可能find一个x
, !p(x)
是真的。 所以notb
不可能是真的所以它一定是假的。 由于notb
是假的,它的否定是真实的。 因此,如果elements
为空,则b
为真,并且elements.All(x => p(x))
必须为真。
这里有更多的方法来思考这个问题。 谓词p
是真的,如果对于所有 x
中的elements
你不能find它是错误的。 但是,如果elements
中没有项目,那么就不可能find任何错误的东西。 因此,对于一个空的集合elements
, p
对于所有的elements
都是真的
那么elements.Any(x => p(x))
什么elements
是一个空的IEnumerable<T>
, p
是一个Predicate<T>
,如上所示? 我们已经知道结果是错误的,因为我们知道它的否定是真实的,但无论如何我们还是要通过它来推理。 直觉是有价值的。 对于elements.Any(x => p(x))
是真的,在p(x)
为真的elements
必须至less有一个x
。 但是,如果elements
中没有任何 x
,就不可能findp(x)
为真的任何 x
。 因此,如果elements
为空,那么elements.Any(x => p(x))
为false。
最后,当s
是一个string
的非空实例时,为什么s.StartsWith(String.Empty)
是true,这里有一个相关的解释 :
如果返回true
的项目数量与所有项目数量相同,则返回true
。 就那么简单:
Driveway.Cars(a => a.Red).Count() == Driveway.Cars.Count()
相关解释: 为什么“abcd”.StartsWith(“”)返回true?
“如果收集是空的,像这样的testing充其量是不确定的,如果我的车道是空的,我不能断言所有停在那里的车都是红色的。
是的你可以。
为了certificate我错了,在空荡的车道上给我一辆不是红色的车。
对于熟悉SQL!的人来说,NULL!= NULL,这是意料之外的行为。
这是SQL的一个怪癖(并不完全正确: NULL = NULL
和NULL <> NULL
都是未定义的,都不会匹配任何行。
Any()
和All()
只是通常的math操作符(“存在的分词”或“存在”)和∀(“通用分词”或“全部”)的实现。
“任何”意味着存在一些谓词为真的项目。 对于空集合,这将是错误的。
“全部”意味着不存在任何谓词错误的项目。 对于空集合,这将永远是真实的。
我认为这是有道理的。 在逻辑上,FOR ALL的补码不是(存在)。 FOR ALL就像All()
。 存在就像Any()
。
所以IQueryable.All()
相当于!IQueryable.Any()
。 如果你的IQueryable
是空的,那么两个都会根据MSDN doc返回true。
在math或计算机科学的其他领域,你会发现这种行为。
在范围无效的情况下(SUM从0到-1),Math中的SUM运算符将返回0(+的中性元素)。 MULTIPYL运算符将返回1(乘法的中立元素)。
现在如果你有布尔expression式,它是非常相似的:OR的中性元素是false
( a OR false = a
),而中性的元素为true
。
现在Linq的ANY
和ALL
都是这样的:
ANY = a OR b OR c OR d ... ALL = a AND b AND c AND d ...
所以如果你有一个math/ cs背景,这个行为就是“你会期望的”。
回归true
也是合乎逻辑的。 你有两个陈述: “有车吗?” 和“红色?” 如果第一个陈述是false
,那第二个陈述是什么并不重要,结果通过modus ponens是true
。
因为任何提议都是空洞的事实 。
False意味着即使没有谓词,查询也不会返回任何结果。 这只是一个不完整的方式,说明什么Dested张贴,因为我input这个。
这与数字零的基本概念非常相似。 即使它代表了缺席的存在,它仍然具有和代表一种价值。 IQueryable.All()应该返回true,因为它将成功返回集合的所有成员。 恰好如此,如果集合是空的,函数不会返回任何成员,但不是因为该函数不能返回任何成员。 只是因为没有成员回来。 这就是说,为什么IQueryable.All()必须经历失败,因为缺乏集合的支持? 它是愿意的,它能够…它是有能力的。 听起来像collections不能阻止他们讨价还价的结束…
All(x => x.Predicate)
与Any(x => !x.Predicate)
相反Any(x => !x.Predicate)
(“所有车辆都是红色的?”与“是否有车辆不是红色的?
Any(x => !x.Predicate)
对于空集合都返回false
(对于“any”的共同理解,这看起来很自然)。
因此, All(x => x.Predicate)
应该(并且)为空集合返回true
。