在另一个可为空的类型上进行可为空类型的等式比较

8
我有两个对象(类型相同),它们都包含一个类型为byte?的属性myprop。这些属性被设置为null。当我执行objectA.myprop.Equals(objectB.myprop)时,结果为“true”,尽管MSDN代码示例表明:“应用于任何 null 对象的 Equals 返回 false。”
我猜测C#在可空类型比较中使用了单独的重载。我想知道在这种情况下C#是如何内部处理有对象和可空类型之分的。
4个回答

10
当您这样调用它时,它会使用文档中所显示的期望行为 Nullable<T>.Equals(object)

(如果返回值是true,则意味着...)

HasValue属性为false,并且另一个参数为null。也就是说,根据定义,两个null值相等。

同样对于通过==进行的相等性检查,在C# 4规范(提升运算符)的第7.3.7节中说明:

对于相等性运算符==[和]!=如果操作数类型都是非空值类型,并且结果类型是bool,则存在一个操作符的提升形式。提升形式是通过将单个?修改器添加到每个操作数类型来构造的。提升运算符认为两个null值相等,而null值与任何非null值不等。如果两个操作数都是非null值,则提升运算符展开操作数并应用基础运算符以产生bool结果。

(强调我自己加的。)
这是关于实现 object.Equals 的一般规则:

对于所有 Equals 方法的实现,以下语句必须为真。在列表中,x、y 和 z 表示不为 null 的对象引用。

[...]

  • x.Equals(null) 返回 false。
因此,虽然这是一个一般规则,但它不适用于这种特定情况——因为这里的值不是对象引用,而是值类型值。这仍然有些令人惊讶,除非你基本上接受 Nullable<T> 是一个有点特殊的情况——它具有特定的 C# 编译器支持和 CLR 支持,涉及装箱和拆箱。

4
我已经为了更加清晰易懂而编辑了这段内容。
你误解了那句话的意思。为了完整呈现上下文,以下是全文:
string s = null;
string t = String.Empty; // Logically the same as ""

// Equals applied to any null object returns false.
bool b = (t.Equals(s));

这段话的意思是,对于一个字符串对象的引用并不等同于一个 null 引用。你断章取义并将其解释为一般规则,但实际上它只适用于引用而非可空类型。
当你处理可空的基本类型时:
实际规则是:
如果两个可空类型都为 null,则它们之间的相等比较结果为 true。

http://msdn.microsoft.com/en-us/library/2cf62fcy.aspx


是的,那将是一个空引用异常。 - Joe
@Chris - 如果是一个 null 对象,那么你说的就是正确的。然而,在可空类型的情况下,就像 Joe 和 Jon 提供的链接所示,情况并非如此。或者你在指 objectA 本身是否为 null? - arviman
@arviman 当我写我的上一个评论时,我能够看到即将发生的事情,并且怀疑开始渗入,但还是想问一下以确保。阅读Jon Skeet的答案后,它使用了HasValue属性,也为我解开了一些疑惑。不过还是感谢你为我澄清了这个问题!=) - Chris
我不同意其中声称这不是一个普遍规则的部分 - 它,但适用于所有引用类型。因此,它不仅限于字符串,但也不适用于Nullable<T>。请参阅我的答案以获取更多详细信息。 - Jon Skeet
我在第一次阅读时忽略了arviman给出的示例涉及到字符串的引用,但他所问的是可空类型。我认为我跟着混淆了。 - Joe
显示剩余4条评论

2

可空类型是一个结构体,而结构体永远不会得到null值,因此当一个可空类型等于null时,这意味着该变量并非真正的null,而是发生了隐式转换,将该变量转换为一个值类型(可空类型)并赋予Value=null属性。所以:

int? a=null;//(a) get a memory space with value property = null
a.GetHashcode();//if (a) really is null must throw a exception but not throw 

0

我认为 Nullable 比较 X==Y 的行为与直觉预期完全一致,但我感觉在 X.Equals(Y) 方面可能存在误解,它的行为就像是一个 static 方法调用。

object A, B;
MyStruct? X, Y;

"Equals applied to any null object returns false."这句话适用于非静态方法A.Equals(B),并且通常应该适用于任何合理的重写。

然而,唯一的原因是A.Equals是属于A的非静态方法,这意味着A永远不可能为null。如果使用A=null尝试A.Equals(B),确实会抛出NullReferenceException

之所以必须如此,是因为由A引用的实例可能比声明变量A更具体化,而这个更具体化的类型可能反过来覆盖A.Equals方法。换句话说,如果A为空,则运行时不知道要使用哪个A.Equals的实现,因此必须抛出异常。

然而,静态方法无法被覆盖,这意味着在编译时已知要运行的方法,所以A可以是null。(这就是为什么调用静态方法比调用它们的非静态对应方法稍微快一些,也是编译器可能决定内联一些静态调用的原因,但我在岔开话题。)

因此,对于任何静态实现,两个操作数都可以是null。此外,null应始终被视为与null相同,并且与任何其他值不同。(如果Equals的实现偏离了这个常见假设,那么通常将被认为是一个错误。)

静态Equals方法的示例:

A==B;
A!=B;
MyClass.Equals(A, B);
object.Equals(A, B);
ReferenceEquals(A, B);

在这方面,Nullable的行为与对象相同。唯一的区别是X.Equals(Y)支持X或Y或两者都为空而不会抛出异常。因此,更正确的说法是它的行为类似于上面提到的静态等式方法,这通常更可取。

但是,X.Equals(Y)如何像静态方法一样运作呢?这是因为Nullable是一个结构体。C#中的结构体不支持继承,因此变量的类型和实例的类型之间没有区别。这就是为什么运行时永远不会怀疑Equals指的是哪个方法。因此,不需要异常。因此,任何由结构体支持的值都可以由该方法支持两个操作数,并且null只是另一个值。

现在,除此之外,还有一些内置的特殊支持语法糖和优化Nullable类型的方法,但是行为仍然保持我上面描述的那样,据我所知。


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