为什么null值在==返回true时,>=返回false?

78

我有两个 int? 类型的变量(或者如果你愿意,可以理解为 Nullable<int>)。我希望对这两个变量进行大于等于(>=)的比较,但是事实证明,如果两个变量都是 null,这将返回 false,而显然 == 运算符会返回 true。

有人能够解释一下这种情况为什么是合理的吗?因为“大于等于”运算符的语义定义中包含了单词“或”。


1
你能发一下产生这种奇怪行为的代码吗? - Android Eve
12
实际上,我会质疑“==”返回true是否合适。我认为这完全不合适。两个值未知的变量怎么能被认定相等呢? - Charles Bretana
3
@Charles,因为如果它们是相同的空值类型(例如int?),那么它们的值是已知的。null - Moo-Juice
3
@moo-juice,在我的世界里不是这样的...如果你的名字是null,你会回答它吗? null不等同于"null"。有一部古老的电影叫做“我的名字叫无名氏”(“孩子们谁打破了玻璃?——“没有人”)。那么,“没有人”是谁?在现实世界中(代码内部之外),null的意思是值是未知的...数据结构的值可能已知(它是Null),但是这个变量代表的真正问题领域实体/值是未知的。 - Charles Bretana
2
@Charles,喜欢这个比喻!但是谁没有打破玻璃? - Moo-Juice
显示剩余11条评论
8个回答

98

当C# 2.0最初设计这个奇怪的特性时,引起了一场巨大的争论。问题在于C#用户完全习惯了它具有意义:

if(someReference == null)

当将等式扩展到可空值类型时,您有以下选择:

  1. 可空相等性是真正的“提升”。如果操作数中的一个或两个为 null,则结果既不为 true 也不为 false,而是 null。在这种情况下,您可以:

    • a) 在 if 语句中禁止使用可空值类型等式,因为 if 语句需要 bool 而不是可空 bool。相反,要比较 null,请要求每个人都使用 HasValue。这很啰嗦和烦人。

    • b) 自动将 null 转换为 false。这样做的缺点是,如果 x 为 null,则 x==null 返回 false,这会与人们对引用类型的 null 比较所理解的相反。

  2. 可空相等性未被提升。可空相等性要么为 true,要么为 false,并且与 null 进行比较是一种 null 检查。这使得可空相等性与可空不等式不一致。

这些选择都没有明显的正确答案;它们都有利弊。例如,VBScript 选择了 1b。经过长时间的讨论,C# 设计团队选择了 #2。


3
“@MCS: In exactly the way that motivates the question in the first place.” 的意思是“正是以激发问题的方式”。"== can be true when <= is false." 可以翻译成“当小于等于号不成立时,等于号可以成立。” - Eric Lippert
1
另一个问题(您没有解决)是当您尝试在其中一个操作数为“null”时执行<<==>>该怎么办。在C#中,答案是返回“false”。在Scala / Java的“String”类中,答案是抛出“NullPointerException”。 - Ken Bloom
3
@Brian:那为什么要允许在可空类型上使用运算符呢?如果它们总是在可空类型的 null 值上抛出异常,那么你可以只在非可空类型上定义运算符,并让用户插入转换为非可空类型,因为这就是他们将要做的来消除异常。 - Eric Lippert
1
">=" 应该等同于 "> || ==",这样规则2仍然保持不变,并且">="和"=="返回相同的值。由于奇怪的可空相等性检查,两个在所有其他上下文中等效的表达式"> || =="和">="在可空相等性检查的情况下并不等效。这对表达式的组合性产生了很大影响,在这种特殊情况下必须加以考虑。 - Pop Catalin
用户可能习惯于允许if(someReference == null),但实际上不应该这样做。这可能是一个方便的权宜之计,但会导致像这样的问题,从而产生内部矛盾的无意义答案。这似乎与成功之坑的本质相反。 - nicodemus13
显示剩余13条评论

60

因为相等性和可比性是分别定义的。
您可以测试 x == nullx > null 毫无意义。在 C# 中它总是为false。


1
+1:这是MSDN链接http://msdn.microsoft.com/en-us/library/2cf62fcy.aspx,但不幸的是,他们忘记解释在两个null的情况下比较运算符的行为(他们只提到了相等性)... - digEmAll
5
是的,但操作符大于等于。我看到了真值表,但我倾向于同意原帖者,">="表示大于等于,如果null==null为真,那么null>=null也应该为真。我猜我们只是将其归结为实现和用户方便,以保留对“==null”检查。 - BlackICE
2
@David,看看Eric的回答,“这些选项都没有明显的正确答案”。但是一般来说,当一个类型是Equatable而不是Comparable时,>=根本没有定义。 - H H

12

'>='的另一种表述方式是:不小于。没有提到相等。在非相等测试中,只要有一个操作数为Null,则结果也为未知(即为Null)。但是,如果你想知道两个操作数是否都为Null,则Null == Null是一个合理的测试(应该返回true)。去掉不等的部分就会产生很大的区别。

下面的代码示例来自http://msdn.microsoft.com/en-us/library/2cf62fcy.aspx#sectionToggle4,总结了C#如何处理Null:

int? num1 = 10;   
int? num2 = null;   
if (num1 >= num2)   
{   
    Console.WriteLine("num1 is greater than or equal to num2");   
}   
else   
{   
    // This clause is selected, but num1 is not less than num2.   
    Console.WriteLine("num1 >= num2 returned false (but num1 < num2 also is false)");   
}   

if (num1 < num2)   
{   
    Console.WriteLine("num1 is less than num2");   
}   
else   
{   
    // The else clause is selected again, but num1 is not greater than   
    // or equal to num2.   
    Console.WriteLine("num1 < num2 returned false (but num1 >= num2 also is false)");   
}   

if (num1 != num2)   
{   
    // This comparison is true, num1 and num2 are not equal.   
    Console.WriteLine("Finally, num1 != num2 returns true!");   
}   

// Change the value of num1, so that both num1 and num2 are null.   
num1 = null;   
if (num1 == num2)   
{   
    // The equality comparison returns true when both operands are null.   
    Console.WriteLine("num1 == num2 returns true when the value of each is null");   
}   

/* Output:   
 * num1 >= num2 returned false (but num1 < num2 also is false)   
 * num1 < num2 returned false (but num1 >= num2 also is false)   
 * Finally, num1 != num2 returns true!   
 * num1 == num2 returns true when the value of each is null   
 */   

这是一个有趣的思维模型。然而,C#规范中的第§1.4节将这些运算符称为“小于或等于”和“大于或等于”。 - Conrad Frix
3
这段话的意思是:这正展示了将编程语言(在这个例子中是C#)翻译成英语时所面临的问题。我认为,无论何时空值涉及到逻辑时,都需要处理三种状态的结果(真、假、未知)。任何涉及到空值的表达式都应该返回未知,唯一的例外是Null == x,它是一个明确测试未知结果为真或为假的条件。 - NealB
@NealB:实际上,规范表明">="和"<="的含义是与人们预期的一样的——第§7.10节明确指出,对于"<="和">="的操作“x op y”旨在等于大于/小于,正如人们所期望的那样。 - nicodemus13

2

>= 操作符用于数值,而 null 不是数值。

您可以 重载 >= 操作符以实现特定类型的需求。


它确实可以在空类型上操作,但会返回false。 - BlackICE
它处理空类型,是的...我们将定义为“操作”的语义。防御性编程; 如果为空,则执行x,而不是在评估过程中将null视为文字值来做决策。 - Aaron McIver
通常情况下,你不能重载运算符,因为你只能在它们自己的类内定义。因此,在这种情况下,你需要访问 Nullable<T> 的代码。 - ANeves

0

由于默认情况下,int 不能为 null,其值将被设置为 0,而为 int 类型构建的 > 和 < 运算符期望使用 values 而不是 nulls

请参阅我对类似问题的答案,在那里我写了一些处理带有 less <greater > 运算符的可空 int 的方法 https://dev59.com/wGkv5IYBdhLWcg3w_Vqn#51507612


0

你会期望什么值?

null == null true

null >= null false

null > null false

null <= null false

null < null false

null != null false

1 == null false

1 >= null false

1 > null false

1 <= null false

1 < null false

1 != null true aka !(1 == null)


0

>= 只有在特定的明确定义方式下使用时才表示“大于或等于”。当用于具有重载运算符的类时,它可以表示类开发人员想要的任何含义。当应用于类似字符串的类时,它可能表示“排序相同或更高”或者可能表示“相同长度或更长”。


-1

NULL不是零(数字或二进制值),也不是零长度的字符串或空白(字符值)。 因此,任何比较运算符在其上始终返回false。 在这里阅读更多相关信息


8
数据库的 NULL 值并不等同于 C# 的 null。此外,在 C# 可空类型上进行比较运算符是一种奇怪的行为,它并不一定遵循通常用于空值比较的规则。 - Joren
3
答案仍然正确,只是链接错误。http://msdn.microsoft.com/zh-cn/library/2cf62fcy.aspx#sectionToggle4 - unholysampler
5
回答是错误的,更重要的是,它基于错误的推理。 - Joren

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