为什么TimeSpan和Guid Structs可以比较为null?

我注意到一些.NET结构可以与null进行比较。 例如:

TimeSpan y = new TimeSpan(); if (y == null) return; 

将编译得很好(与Guid结构相同)。
现在我知道stucts是值类型,并且上面的代码不应该编译,除非有一个operator ==的重载,它接受一个对象。 但是,据我所知,没有。
我用Reflector和MSDN上的文档查看了这个类。
他们两个确实实现了以下接口:

 IComparable, IComparable, IEquatable 

但是,尝试使用相同的接口似乎没有帮助:

 struct XX : IComparable, IComparable, IEquatable { public int CompareTo(Object obj) { return 0; } public int CompareTo (XX other){ return 0; } public bool Equals (XX other){ return false; } public override bool Equals(object value){ return false; } public static int Compare(XX t1, XX t2){ return 0; } } 

我正在使用:.NET 2.0 Visual Studio 2005。

有谁知道这是什么原因? 我只是想更好地理解。 这不是问题,因为我知道我不应该将结构与null进行比较。

这是==运算符。

TimeSpan类具有相等运算符的重载:

 public static bool operator ==(DateTime d1, DateTime d2) { return (t1._ticks == t2._ticks); } 

这本身不能与null比较, ……

随着可空类型的到来, 每个结构都可以隐式转换为可空类型 ,所以当你看到类似的东西时

 TimeSpan y = new TimeSpan(); if (y == null) return; 

没有看到这种情况发生:

 TimeSpan y = new TimeSpan(); if ((Nullable)y == (Nullable)null) return; 

Null获取隐式转换(隐式赋值?),但并非所有System.Object对象都执行:

 TimeSpan y = new TimeSpan(); object o = null; if (y == o) //compiler error return; 

好的,但是相等运算符不会采用可为空的参数,是吗?

好吧, msdn在这里有所帮助,说明:

预定义的一元和二元运算符以及值类型存在的任何用户定义的运算符也可以由可空类型使用。 如果[任何]操作数为null,则这些运算符产生空值; 否则,运算符使用包含的值来计算结果。

因此,您可以免费获得每个运算符的可空实现 ,并具有固定的定义行为。 上面提到的“包含值”是非可空运算符返回的实际值。

当包含可空类型时,有效地引入了该问题。 有从TimeSpanTimeSpan?的隐式转换TimeSpan?TimeSpan?之间有比较TimeSpan? 和该类型的空值。

编译器会针对某些类型发出警告,这使得它更清楚它正在尝试执行的操作:

 int x = 10; if (x == null) { Console.WriteLine(); } 

给出这个警告:

 Test.cs(9,13): warning CS0472: The result of the expression is always 'false' since a value of type 'int' is never equal to 'null' of type 'int?' 

我相信马克·格拉维尔和我一起制定了警告的情况……这是一种耻辱,这是不一致的。

在C#语言规范的第7.9.6节中,这种情况适用于generics。

即使T可以表示值类型,也允许使用x == null结构,并且当T是值类型时,结果简单地定义为false。

我挖了一下这个规格,但找不到更一般的规则。 Jon的回答表明这是一个可以取消的促销问题。

这个规则(或类似的变化)似乎确实在这里应用。 如果仔细观察reflection输出,你会发现比较不存在。 C#编译器显然正在优化这种比较并将其替换为false。

例如,如果键入以下内容

 var x = new TimeSpan(); var y = x == null; Console.WriteLine(x); 

然后反编译你会看到以下内容

 var x = new TimeSpan(); var y = false; Console.WriteLine(x); 

另请参阅: C#3(.NET 3.5)版本的csc无法报告CS0162的不可用代码(struct / null)

从C#3编译器开始,这意味着它有时甚至不会警告你这个; -p

因为Guid / TimeSpan等提供了== ,它们会陷入这个不会警告你的陷阱。

我发现它 🙂

以下是警告:

 int i = 0; if (i == null) // ^^ Warning: The result of the expression is always 'false' since a value of // type 'int' is never equal to 'null' of type 'int?' 

编译器是否未能发出正确的警告,即您键入的null已转换为TimeSpan?类型TimeSpan? 为了比较。

编辑:规范中的相关部分是§13.7.1,声明null可以隐式转换为任何可空类型,并且(非常难以阅读)部分§13.7.2声明值类型T可以隐式转换为T?

我最初写的:

无论发生什么事都是C#规范中的事情,因为就像JaredPar所说的那样编译为false

请注意,这不会编译:

 TimeSpan ts = new TimeSpan(); object o = null; if (ts == o) // error, Operator '==' cannot be applied to operands of type 'System.TimeSpan' and 'object' ...