C# 可空相等运算,为什么 null <= null 解析为 false?
为什么在 .NET 中
null >= null
解析为 false,但
null == null
解析为 true?
换句话说,为什么 null >= null
不等于 null >空 || null == null
?
有谁有官方的答案吗?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(11)
这个问题似曾相识,哦等等,它是...
为什么当 == 对于 null 值返回 true 时,>= 返回 false?
我从另一个答案中记得的是:
This question was a Deja vu, oh wait here it is...
Why does >= return false when == returns true for null values?
The thing I remember from the other answer was :
此行为在 C# 规范 (ECMA-334) 中定义在第14.2.7节中(我已经突出显示了相关部分):
特别是,这意味着通常的关系法则不成立;
x >= y
并不意味着!(x < y)
。血淋淋的细节
有些人问为什么编译器首先决定这是
int?
的提升运算符。我们来看看吧。 :)我们从 14.2.4“二元运算符重载解析”开始。这详细说明了要遵循的步骤。
首先,检查用户定义的运算符的适用性。这是通过检查
>=
两侧的类型定义的运算符来完成的...这提出了null
的类型是什么的问题!null
文字实际上在给定类型之前没有任何类型,它只是“空文字”。通过遵循 14.2.5 中的指示,我们发现这里没有合适的运算符,因为 null 文字没有定义任何运算符。此步骤指示我们检查预定义运算符集的适用性。 (本节也排除了枚举,因为双方都不是枚举类型。)相关的预定义运算符在第 14.9.1 到 14.9.3 节中列出,它们都是原始数字类型的运算符,以及这些运算符(请注意,此处不包括
string
运算符)。最后,我们必须使用这些运算符和 14.4.2 中的规则执行重载决策。
实际上执行这个解决方案会非常乏味,但幸运的是有一个捷径。第 14.2.6 节给出了重载决策结果的信息示例,其中指出:
由于两边都是
null
,我们可以立即抛出所有未提升的运算符。这给我们留下了所有原始数字类型的提升数字运算符。然后,使用前面的信息,我们选择第一个存在隐式转换的运算符。由于 null 文字可以隐式转换为可为 null 的类型,并且
int
存在可为 null 的类型,因此我们从列表中选择第一个运算符,即int? >= int?
。This behaviour is defined in the C# specification (ECMA-334) in section 14.2.7 (I have highlighted the relevant part):
In particular, this means that the usual laws of relations don't hold;
x >= y
does not imply!(x < y)
.Gory details
Some people have asked why the compiler decides that this is a lifted operator for
int?
in the first place. Let's have a look. :)We start with 14.2.4, 'Binary operator overload resolution'. This details the steps to follow.
First, the user-defined operators are examined for suitability. This is done by examining the operators defined by the types on each side of
>=
... which raises the question of what the type ofnull
is! Thenull
literal actually doesn't have any type until given one, it's simply the "null literal". By following the directions under 14.2.5 we discover there are no operators suitable here, since the null literal doesn't define any operators.This step instructs us to examine the set of predefined operators for suitability. (Enums are also excluded by this section, since neither side is an enum type.) The relevant predefined operators are listed in sections 14.9.1 to 14.9.3, and they are all operators upon primitive numeric types, along with the lifted versions of these operators (note that
string
s operators are not included here).Finally, we must perform overload resolution using these operators and the rules in 14.4.2.
Actually performing this resolution would be extremely tedious, but luckily there is a shortcut. Under 14.2.6 there is an informative example given of the results of overload resolution, which states:
Since both sides are
null
we can immediately throw out all unlifted operators. This leaves us with the lifted numeric operators on all primitive numeric types.Then, using the previous information, we select the first of the operators for which an implicit conversion exists. Since the null literal is implicitly convertible to a nullable type, and a nullable type exists for
int
, we select the first operator from the list, which isint? >= int?
.许多答案都符合规范。事实证明这是一个不寻常的事件,C# 4 规范没有证明特别提到的比较两个空文字的行为的合理性。事实上,严格阅读规范表明“null == null”应该给出歧义错误! (这是由于在为 C# 3 做准备的 C# 2 规范清理过程中发生的编辑错误造成的;规范作者无意将其定为非法。)
如果您不相信我,请仔细阅读规范。它表示在 int、uint、long、ulong、bool、decimal、double、float、string、enum、委托和对象上定义了相等运算符,以及所有值类型运算符的提升为 null 版本。
现在,我们立即遇到一个问题;这个集合无限大。在实践中,我们不会在所有可能的委托和枚举类型上形成所有运算符的无限集。需要在此处修复规范,以注意添加到候选集中的枚举和委托类型的唯一运算符是属于任一参数类型的枚举或委托类型的运算符。
因此,我们将枚举和委托类型保留在其中,因为这两个参数都没有类型。
我们现在有一个重载解决问题;我们必须首先排除所有不适用的算子,然后再确定最佳的适用算子。
显然,在所有不可空值类型上定义的运算符都是不适用的。这使得运算符可以为空值类型、字符串和对象。
我们现在可以出于“更好”的原因消除一些。更好的运算符是具有更具体类型的运算符。整数?比任何其他可为空的数字类型更具体,因此所有这些都被消除。字符串比对象更具体,因此对象被淘汰。
那么剩下字符串、整数的相等运算符吗?和布尔?作为适用的运营商。哪一个最好? 没有一个比另一个更好。因此这应该是一个歧义错误。
为了使这种行为在规范中得到证实,我们必须修改规范,以注意“null == null”被定义为具有字符串相等的语义,并且它是编译-时间常数为真。
事实上我昨天才发现这个事实;你居然问这个,真是奇怪。
要回答其他答案中提出的问题,即为什么
null >= null
给出关于 int 比较的警告? ——好吧,应用与我刚才相同的分析。不可空值类型上的>=
运算符不适用,剩下的运算符中,int? 上的运算符不适用。是最好的。>=
没有歧义错误,因为在 bool 上没有定义>=
运算符?或字符串。编译器正确地将运算符分析为两个可为空整数的比较。要回答有关为什么空值(而不是文字)上的运算符具有特定异常行为的更普遍的问题,请参阅我对重复问题的回答。它清楚地解释了证明这一决定合理性的设计标准。简而言之:对 null 的操作应该具有对“我不知道”的操作的语义。您不知道的一个量是否大于或等于另一个您不知道的量?唯一明智的答案是“我不知道!”但我们需要将其转换为布尔值,而合理的布尔值是“假”。但是,在比较相等性时,大多数人认为 null 应该等于 null,即使比较两个您不知道相等性的事物也应该导致“我不知道”。这一设计决策是权衡许多不良结果的结果,以找到使该功能发挥作用的最不坏的结果;我同意,这确实使语言有些不一致。
A number of answers appeal to the spec. In what turns out to be an unusual turn of events, the C# 4 spec does not justify the behaviour specifically mentioned of comparison of two null literals. In fact, a strict reading of the spec says that "null == null" should give an ambiguity error! (This is due to an editing error made during the cleanup of the C# 2 specification in preparation for C# 3; it is not the intention of the spec authors to make this illegal.)
Read the spec carefully if you don't believe me. It says that there are equality operators defined on int, uint, long, ulong, bool, decimal, double, float, string, enums, delegates and objects, plus the lifted-to-nullable versions of all the value type operators.
Now, immediately we have a problem; this set is infinitely large. In practice we do not form the infinite set of all operators on all possible delegate and enum types. The spec needs to be fixed up here to note that the only operators on enum and delegate types which are added to the candidate sets are those of enum or delegate types that are the types of either argument.
Let's therefore leave enum and delegate types out of it, since neither argument has a type.
We now have an overload resolution problem; we must first eliminate all the inapplicable operators, and then determine the best of the applicable operators.
Clearly the operators defined on all the non-nullable value types are inapplicable. That leaves the operators on the nullable value types, and string, and object.
We can now eliminate some for reasons of "betterness". The better operator is the one with the more specific types. int? is more specific than any of the other nullable numeric types, so all of those are eliminated. String is more specific than object, so object is eliminated.
That leaves equality operators for string, int? and bool? as the applicable operators. Which one is the best? None of them is better than the other. Therefore this should be an ambiguity error.
For this behaviour to be justified by the spec we are going to have to emend the specification to note that "null == null" is defined as having the semantics of string equality, and that it is the compile-time constant true.
I actually just discovered this fact yesterday; how odd that you should ask about it.
To answer the questions posed in other answers about why
null >= null
gives a warning about comparisons to int? -- well, apply the same analysis as I just did. The>=
operators on non-nullable value types are inapplicable, and of the ones that are left, the operator on int? is the best. There is no ambiguity error for>=
because there is no>=
operator defined on bool? or string. The compiler is correctly analyzing the operator as being comparison of two nullable ints.To answer the more general question about why operators on null values (as opposed to literals) have a particular unusual behaviour, see my answer to the duplicate question. It clearly explains the design criteria that justify this decision. In short: operations on null should have the semantics of operations on "I don't know". Is a quantity you don't know greater than or equal to another quantity you don't know? The only sensible answer is "I don't know!" But we need to turn that into a bool, and the sensible bool is "false". But when comparing for equality, most people think that null should be equal to null even though comparing two things that you don't know for equality should also result in "I don't know". This design decision is the result of trading off many undesirable outcomes against one another to find the least bad one that makes the feature work; it does make the language somewhat inconsistent, I agree.
编译器推断,在比较运算符的情况下,
null
被隐式键入为int?
。Visual Studio 提供警告:
这可以使用以下代码进行验证:
输出:
为什么?
这对我来说似乎是合乎逻辑的。首先,这是 的相关部分C# 4.0 规范。
空文字§2.4.4.6:
二进制数字促销§7.3.6.2:
提升运算符§7.3.7:
空文字本身并没有真正的类型。它是通过分配给什么来推断的。然而,这里没有进行任何分配。仅考虑具有语言支持的内置类型(具有关键字的类型)、
object
或任何可为 null 的类型都是不错的选择。但是object
不具有可比性,因此它被排除在外。这使得可空类型成为了很好的候选者。但哪种类型呢?由于左操作数和右操作数都没有指定类型,因此默认情况下它们会转换为(可空)int
。由于两个可为 null 的值均为 null,因此它返回 false。The compiler is inferring that in the case of the comparison operators, the
null
is being implicitly typed asint?
.Visual Studio offers a warning:
This can be verified with the following code:
Outputs:
Why?
This seems logical to me. First of all, here's relevant sections of the C# 4.0 Spec.
The null literal §2.4.4.6:
Binary numeric promotions §7.3.6.2:
Lifted operators §7.3.7:
The null-literal alone doesn't really have a type. It is inferred by what it is assigned to. No assignment takes place here however. Only considering built-in types with language support (ones with keywords),
object
or any nullable would be a good candidate. Howeverobject
isn't comparable so it gets ruled out. That leaves nullable types good candidates. But which type? Since neither left nor right operands has a specified type, they are converted to a (nullable)int
by default. Since both nullable values are null, it returns false.编译器似乎将 null 视为整数类型。
VS2008备注:
“与'int'类型的null比较?”总是产生“假””
It seems like the compiler treats null as if they're integer types.
VS2008 remarks:
"Comparing with null of type 'int?' always produces 'false'"
这是因为编译器足够聪明,可以发现
>= null
将始终为 false,并将表达式替换为常量值false
。查看此示例:
编译为以下代码:
This is because the compiler is smart enough to figure out that
>= null
will always be false and replaces your expression with a constant value offalse
.Check out this example:
This compiles down to the following code:
当我跑步时,
我收到一条警告:
但我想知道为什么它被转换为 int 。
When I run
I get a warning saying:
I wonder why it is casted to an int though.
这不是“官方”答案,这是我的最佳猜测。但是,如果您正在处理可为空的整数并比较它们,那么如果您正在处理两个为空的“int?”,您很可能总是希望比较返回 false。这样,如果它返回 true,您可以确定您实际上比较的是两个整数,而不是两个空值。它只是消除了单独的空检查的需要。
也就是说,如果这不是您期望的行为,则可能会令人困惑!
This isn't the "official" answer, it's my best guess. But if you're dealing with nullable ints and comparing them, you most likely always want the comparison to return false if you're dealing with two "int?"s that are null. That way if it returns true, you can be sure you've actually compared two integers, not two null values. It just removes the need for a separate null check.
That said, it is potentially confusing if it's not the behaviour you expect!
此行为记录在有关可空类型的页面中。它没有给出真正的解释,但我的解释如下。对于
null
而言,>
和<
没有任何意义。null
表示没有值,因此仅等于另一个也没有值的变量。由于>
和<
没有任何意义,因此将其转入>=
和<=.
The behavior is documented in this page about Nullable Types. It doesn't give a real explanation for why, but my interpretation is the following.
>
and<
have no meaning when with regards tonull
.null
is the absence of a value and thus is only equal to another variable that also has no value. Since there is no meaning for>
and<
, that is carried over to>=
and<=
.他们似乎混合了 C 和 SQL 的范例。
在可为空变量的上下文中,null == null 实际上应该产生 false,因为如果两个值都不知道,则相等就没有意义,但是对于整个 C# 而言,在比较引用时这样做会导致问题。
They seem to be mixing paradigms from C and SQL.
In the context of nullable variables, null == null should really yield false since equality makes no sense if neither value is known, however doing that for C# as a whole would cause issues when comparing references.
简短的答案是“因为这就是规范中定义这些运算符的方式”。
来自 ECMA C# 规范 的第 8.19 节:
The short answer would be "because that's the way those operators are defined in the specification".
From section 8.19 of the ECMA C# spec: