为什么可以将非空类型与null进行比较?

17
如果我在C#中尝试将null赋值给非可空类型:

可能重复:
C#允许将值类型与null进行比较

System.DateTime time = null;

我会得到一个编译时错误:

错误 CS0037:无法将 null 转换为 'System.DateTime',因为它是一个非可空值类型

这很有道理。但如果将相同的类型与 null 进行比较:

System.DateTime time = obtainFromSomewhere();
if( time == null ) {
    //whatever;
}

没有编译时错误。这对我来说没有意义 - 如果我不能分配null,那么为什么它会是null

为什么我可以将非可空类型与null进行比较?


2
因为该语句本身是有效的。您始终可以检查对象是否为空。 - halfdan
1
因为允许将非可空类型隐式转换为可空类型,所以它不是有效的吗? - username
4
请参考Eric Lippert在Comparing structs to nullC# okay with comparing value types to null中提供的优秀答案(目前这里没有一个答案是正确的)。 - Dirk Vollmar
2
@0xA3:Brian的回答基本上是正确的,但是你也是对的,这是一个重复的问题。@sharptooth:如果你将内置类型如int与null进行比较,编译器会给出一个警告,说明比较总是为false,但是对于可提升的用户定义的相等运算符,编译器不会给出警告;这是编译器的缺陷。我为没有警告感到抱歉;我们真的应该在这里添加一个警告。 - Eric Lippert
5个回答

18
这样做对于DateTime有效的原因是,DateTime定义了自己的==运算符。由于这样做,它获得了一个可用于DateTime?的提升版本的运算符。由于DateTimenull都可以隐式转换为DateTime?,所以比较会编译通过,但在运行时始终计算为false。
感谢Matt Ellen指出我的原回答没有涵盖问题中的示例。

2
但在Sharptooth的示例中,没有类型参数,因此它不应该编译。除非两个操作数都是引用类型或“null”,否则应该出现编译时错误。 - Matt Ellen
实际上,在您的引用上面有这样一句话:“预定义的引用类型相等运算符不允许比较值类型操作数。因此,除非结构体类型声明其自己的相等运算符,否则无法比较该结构体类型的值。” - Matt Ellen

5

这是由于装箱造成的。

DateTime可以被装箱为object,因此它变成了一个引用,可以与null进行比较(尽管它始终为false)。

然而,一个对象(null)不能被拆箱回DateTime,因此它不能被赋值给DateTime

例如:

object now = DateTime.Now;
bool isNull = now == null

编辑: 正如Brian Rasmussen指出的那样,我的装箱理论是错误的。只有在像我的示例或(object)DateTime.Now == null中显式转换为对象时才会发生装箱。


他应该使用DataTime?而不是DateTime!这是一个常见的做法。 - Farzin Zaker
@Farzin Zaker:这里提出的问题是“为什么我可以将非空类型与null进行比较?”,而不是“我应该使用什么”。 - František Žiačik
1
没有装箱。检查生成的 IL。 - Brian Rasmussen
@Brian Rasmussen:你说得对。 - František Žiačik
4
这并不是由于拳击导致的。C# 4.0规范第7.6.10节指出:“预定义的引用类型相等运算符从不会对其操作数执行拳箱转换操作。 - Eric Lippert

2

自 .NET 2.0 开始存在对可空类型的隐式转换(请参见 Eric Lippert 在这里 的说法)。

编译器会给出以下警告,指示发生了转换:

C:\>c:\windows\Microsoft.NET\Framework\v2.0.50727\csc test.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.4927
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
test.cs(16,12): warning CS0464: Comparing with null of type 'int?' always produces 'false'

在没有可空类型的 .NET 1.1 中,您的代码示例将不合法:

在C:\目录下,使用c:\windows\Microsoft.NET\Framework\v1.1.4322\csc编译test.cs文件。此处使用的是Microsoft (R) Visual C# .NET编译器版本7.10.3052.4,用于Microsoft (R) .NET Framework 1.1.4322。然而,在第12行第13列出现了错误,提示CS0019错误:无法将“System.DateTime”类型和“”进行比较运算符“==”。

1

它应该是一个可空类型:

System.DateTime? time = null;

你的代码应该像这样:

System.DateTime? time = obtainFromSomewhere();
if( time.HasValue ) {
    //use time.Value;
}

但请记住,您的obtainFromSomewhere函数应返回DateTime?类型。


嗯.. 应该是 if( time.HasValue ) 吧? - sharptooth
是的,这就是我在问的原因。如果获取到了 null,我们如何“使用”.Value呢? - sharptooth
如果时间没有价值,就利用时间的价值... - Jaymz
不,你应该检查HasValue属性是否为真,然后使用Value属性。 - Farzin Zaker

1
在我看来,这是允许的,因为NULL不是来自任何类型的真实值。
像下面这样的代码是被允许的,这是很好的:
System.DateTime time = obtainFromSomewhere(); // allways a date
System.DateTime? otherTime = obtainFromSomewhereElse(); // null if nothing planned

if (time == otherTime)
{
    // match
    ...
}

没有了 null,我们该怎么办呢?


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