x is null 和 ReferenceEquals(x, null) 有区别吗?

8

当我写下这句话:

ReferenceEquals(x, null)

Visual Studio建议可以简化空值检查,并将其简化为:
x is null

这两个是否真的相同?


1
它们是等价的,是的。虽然前者更为广泛地被理解。但前者更难理解。- https://dev59.com/AaXja4cB1Zd3GeqPXNbM - mjwills
虽然在底层,ReferenceEquals 只是调用了 return a == b - 请参见源代码,因此超常见的 x == null 也同样有效。 - Rhumborl
4
@Rhumborl:不会,只有当x的类型没有重载operator==时才会。 ReferenceEquals永远不会触发该重载(因为它在静态类型为“object”的值上运行),但是如果您自己在代码中编写==,那么很可能会发生这种情况。 - Jeroen Mostert
2
@Rhumborl 是的,ReferenceEquals 只是调用 a == b。然而,由于它接受 object 类型的参数,ReferenceEquals(x,null) 并不等同于 x == null,而是等同于 ((object)x) == ((object)null) - relatively_random
4个回答

11

我注意到很多答案都指出 x == null, x is null, 和 ReferenceEquals(x, null) 在大部分情况下都是等价的,但是有一种情况你不能使用 x == null,如下所述:

请注意,以下代码假设您已为您的类实现了Equals方法:

不要这样做 - 运算符==方法将递归调用直到发生堆栈溢出:

public static bool operator ==(MyClass x1, MyClass x2)
{
   if (x1 == null)
      return x2 == null;

   return x1.Equals(x2)
}

不要这样做,应该这样:

public static bool operator ==(MyClass x1, MyClass x2)
{
   if (x1 is null)
      return x2 is null;

   return x1.Equals(x2)
}

或者

public static bool operator ==(MyClass x1, MyClass x2)
{
   if (ReferenceEquals(x1, null))
      return ReferenceEquals(x2, null);

   return x1.Equals(x2)
}

你可以使用==,只要通过将操作数强制转换为object来强制执行默认的object ==比较。 - Jeff Mercado

3
这两者从语义上来说是一样的(假设x不是值类型)。你正在进行一个空引用检查,对于所有引用类型都是相同的。
然而在实现上有所不同。x == null 或 x is null 将直接被实现为IL指令,但 Object.ReferenceEquals(x, null) 将成为一个方法调用。
另外请注意,如果x的类型已重载 operator == ,则 x == null 可能不等同于null检查(在运算符重载中更改null检查的语义是非常差的代码,因为没有人会预料到这种语义变化)。
注1:当然,优化器可以识别这一点并输出IL,您需要查看IL以确认此事。

2
我意识到我来晚了,答案已经给出了,但我觉得有必要总结一下,因为每8-12个月我都会搜索这个问题,我希望能理解一个解释(如果它被发布的话)...
1. ReferenceEquals(a,b)
这是执行安全引用相等比较的可靠方法。它基本上执行(object)a == (object)b(或类似的操作),并具有以下优点:它的使用可以立即被识别,并且它不能被覆盖。

2. a == b

这种方法对大多数人来说感觉“自然”(因为在C#中进行的大多数比较都将使用此运算符)。

引用类型的默认行为应该是正确的。但是,这可以被重载,这可能会导致意外的结果(想象一下运算符重载的失败实现)。

正如@mdebeus所说,另一个风险(即使是对于已阅读C#入门指南的熟练猴子而言也是微不足道的)是导致StackOverflowException。当重载==和!=并在方法内部使用运算符时,这可能会出现。


3. a是b

好的,这是一种闪亮新颖的甜蜜物品。Microsoft在这种情况下用is来描述:

is运算符检查表达式结果的运行时类型是否与给定类型兼容。

[...]

E is T表达式返回true,如果E的结果非空并且可以通过引用转换、装箱转换或拆箱转换将其转换为类型T;否则,它返回false。is运算符不考虑用户定义的转换。

(阅读完整描述此处)

简而言之,如果a可以通过装箱、拆箱或协变转换为b,则将返回true。正如您所期望的那样,这对null非常有效。


总的来说,作为个人笔记,虽然is在等式重载中进行空引用检查可以使代码更短更美观,但我认为我仍然会使用ReferenceEquals,因为我是一个控制狂,而且在协变性的情况下,is的工作方式至少有一部分让我感到担忧。

1
在这种情况下,它们的意思相同。大多数人会使用“x == null”。我想“ReferenceEquals”可能会有点令人困惑,因为实际上“null”是一个字面量,表示“没有引用”。任何引用怎么能等于“没有引用”呢?请注意,“x is null”只允许在C#7中使用,它是模式匹配功能。通常使用“is”来检查“x”是否是兼容类型,但“null”不是一种类型。所以这也有点令人困惑。这就是为什么我更喜欢“x == null”的原因。

那其实就是让我感到困惑的地方。 - Michael Haddad
1
另外,为什么 x is null 是可行的?根据 MSDN:“[is] 检查对象是否与给定类型兼容”。null 是一种类型吗? - Michael Haddad
1
@Sipo:你说得对,这就是为什么我更喜欢使用== null而不是is null的原因;-) - Tim Schmelter
7
我将此答案降级,因为它可能会导致堆栈溢出,对于那些实现operator==(x)并在该方法中调用x == null的对象。这就是为什么有ReferencEquals方法或者你需要使用x is null的原因。 - mdebeus

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