Equals(item, null) or item == null Equals(item, null) 或者 item == null

49
使用static Object.Equals来检查null的代码比使用==运算符或regular Object.Equals的代码更健壮吗?后两者不容易被覆盖以至于检查null不按预期工作(例如,当比较的值 null时返回false)。
换句话说,这样做是否更好:
if (Equals(item, null)) { /* Do Something */ }

比这个更强大:
if (item == null) { /* Do Something */ }

我个人觉得后一种语法更容易阅读。在编写处理作者无法控制的对象(例如库)的代码时,应该避免使用后一种语法吗?在检查是否为空时,它应该始终被避免吗?这只是纠结吗?

另一个选项是is运算符:if (item is null),从C# 7+版本开始可用。请参阅:“x is null”和“x == null”之间有什么区别? - undefined
还有相关内容:在C#中检查对象是否为空 - undefined
7个回答

88

这个问题没有简单的答案。 在我看来,任何人都建议您总是使用其中一个方法而不是另一个方法都是错误的建议。

实际上,您可以调用几种不同的方法来比较对象实例。给定两个对象实例ab,您可以编写以下代码:

  • Object.Equals(a,b)
  • Object.ReferenceEquals(a,b)
  • a.Equals(b)
  • a == b

这些方法可能会执行不同的操作!

Object.Equals(a,b)(默认情况下)将在引用类型上执行引用相等性比较,在值类型上执行按位比较。从MSDN文档中可以看到:

 

Equals的默认实现支持引用类型的引用相等性,并对值类型执行按位相等性。引用相等性意味着进行比较的对象引用指向同一对象。按位相等性意味着进行比较的对象具有相同的二进制表示形式。

    

请注意上面的最后一段...稍后我们将讨论这个问题。

Object.ReferenceEquals(a,b)仅执行引用相等性比较。如果传递的类型是装箱的值类型,则结果始终为false

a.Equals(b)调用Object的虚拟实例方法,而a的类型可以覆盖该方法以执行任何想要的操作。使用虚拟调度执行调用,因此运行的代码取决于a的运行时类型。

a == b调用a的**编译时类型**的静态重载运算符。如果该运算符的实现在ab上调用实例方法,则它也可能依赖于参数的运行时类型。由于分派基于表达式中的类型,因此以下内容可能会产生不同的结果:

Frog aFrog = new Frog();
Frog bFrog = new Frog();
Animal aAnimal = aFrog;
Animal bAnimal = bFrog;
// not necessarily equal...
bool areEqualFrogs = aFrog == bFrog;
bool areEqualAnimals = aAnimal = bAnimal;

因此,使用operator ==检查空值存在漏洞。 实际上,大多数类型不会重载==——但这从来没有保证。

在这里,实例方法Equals()也不比较好。虽然默认实现执行引用/位相等性检查,但是一种类型可以覆盖Equals()成员方法,在这种情况下将调用该实现。用户提供的实现可以返回任何它想要的内容,即使与null进行比较。

那么你问的关于静态版本的Object.Equals()呢?它会运行用户代码吗?嗯,事实证明答案是肯定的。 Object.Equals(a,b)的实现类似于:

((object)a == (object)b) || (a != null && b != null && a.Equals(b))

你可以自己尝试一下:

class Foo {
    public override bool Equals(object obj) { return true; }  }

var a = new Foo();
var b = new Foo();
Console.WriteLine( Object.Equals(a,b) );  // outputs "True!"

因此,当调用语句 Object.Equals(a,b) 时,即使调用中的类型都不是 null,也有可能运行用户代码。请注意,当任一参数为 null 时,Object.Equals(a,b) 不会调用实例版本的 Equals()

简而言之,您获得的比较行为可能会因选择调用的方法而显着变化。然而,在此要注意的一点是:微软并没有正式记录 Object.Equals(a,b) 的内部行为。 如果您需要保证与 null 引用进行比较而不运行任何其他代码,则应使用 Object.ReferenceEquals()

Object.ReferenceEquals(item, null);

这个方法非常明确地表达了你特别期望的结果是两个引用进行引用相等比较。与使用Object.Equals(a,null)之类的东西相比,这里的好处在于,不太可能有人后来会这样说:

"嘿,这很尴尬,让我们换成:a.Equals(null)或者a == null "

这可能会导致不同的结果。

然而,让我们注入一些实用主义。到目前为止,我们已经谈论了不同比较方式可能产生不同结果的潜力。虽然这当然是正确的,但是对于某些类型,像StringNullable<T>这样的内置.NET类具有定义良好的比较语义。此外,它们是sealed - 通过继承防止其行为发生任何变化。以下代码非常常见(也是正确的):

string s = ...
if( s == null ) { ... }

写下以下代码是不必要的(也很丑陋):

if( ReferenceEquals(s,null) ) { ... }

因此,在某些有限的情况下,使用==是安全和合适的。


6
类似“我应该总是/从不做X”的问题,暗示了对所讨论主题的细微差别的知识缺口。我认为在这里提供更多细节会有所帮助,以澄清为什么我认为简单的答案没有意义。 - LBushkin
@LukeH:我添加了一些叙述来谈论这个案例。我最初只关注于实例与null的比较,但值得解释一下。感谢您的反馈。 - LBushkin
非常有用的答案。然而,在讨论字符串(以及任何其他在内部是引用但在逻辑上作为值的类型)时,它混淆了ReferenceEquals和Object.Equals之间的区别。说“写ReferenceEquals(myString, null)是不必要和丑陋的”忽略了这样做是完全错误的事实。 - ToolmakerSteve
4
在我的看法中,在实践中,你应该使用"a == b",除非你知道你需要引用比较。如果一个类型不支持"=="运算符,则使用Object.Equals(a, b)。我建议永远不要使用"a.Equals(b)",因为如果a为空,就会导致异常。如果某个类型的实现未能正确处理Object.Equals,则这将是实现中的错误,而不是您使用它时的错误。不要费力避免可能有人编写了一个糟糕的类。 - ToolmakerSteve
1
有趣的是,Visual Studio建议将ReferenceEquals(foo,null)转换为"foo is null"。 - Matt Kerr
显示剩余2条评论

6

if (Equals(item, null))不比if (item == null)更加健壮,而且我觉得它更加令人困惑。


有可能某人在没有覆盖“Equals”的情况下重载“==”运算符。这将是一个糟糕的设计,但两者之间的相等比较语义完全有可能存在差异。此外,如果重载了“==”运算符,则它可能与内置的“Objects.Equals()”方法完全不同 - 我认为该方法只检查引用相等性。 - LBushkin
1
如果您对您正在检查的对象如此偏执,那么(null == obj)不会保证参考比较吗?即使==被重载,按定义的结果也将是预期的行为。如果这不是预期的行为,那么这是一个超出本问题范围的问题。 - Tezra

5

当您想测试IDENTITY(在内存中相同的位置)时:

ReferenceEquals(a, b)

处理空值。不可重写。百分之百安全。

但请确保您真的想要进行IDENTITY测试。考虑以下情况:

ReferenceEquals(new String("abc"), new String("abc"))

这将返回false。相比之下:

Object.Equals(new String("abc"), new String("abc"))

(new String("abc")) == (new String("abc"))

都会返回true

如果您期望在这种情况下得到一个true的答案,则需要进行EQUALITY测试,而不是IDENTITY测试。查看下一部分。


当您想测试EQUALITY(相同内容)时:

  • 如果编译器不抱怨,使用"a == b"。

  • 如果被拒绝(如果变量a的类型没有定义"=="运算符),则使用"Object.Equals(a, b)"。

  • 如果您在已知a不为null的逻辑内部,则可以使用更具可读性的"a.Equals(b)"。例如,"this.Equals(b)"是安全的。或者如果"a"是在构造时初始化的字段,并且构造函数在传递null作为该字段要使用的值时引发异常。

现在,针对原始问题:

Q: 这些是否容易被某个类覆盖,其中代码不能正确处理null,从而导致异常?

A:是的。唯一获得百分之百安全EQUALITY测试的方法是自己预先测试null。

但是您应该吗?错误将出现在(假想的未来不良类)中,并且它将是一种直接类型的故障。由谁提供类来调试和修复很容易。我怀疑这经常发生或长时间存在。

更详细的答案:Object.Equals(a, b)最有可能在面对编写不良的类时正常工作。如果"a"为null,则Object类本身将处理它,因此没有风险。如果"b"为null,则运行时而不是编译时的"a"的动态类型确定了何时调用"Equals"方法。被调用的方法只需在"b"为null时正常工作即可。除非所调用的方法编写得极其不良,否则它的第一步将确定"b"是否为它所理解的类型。

所以Object.Equals(a, b)是可读性、编码难度和安全性之间的合理折衷。

2

框架指南 建议您将 Equals 视为值相等性(检查两个对象是否表示相同的信息,即比较属性),并将 == 视为引用相等性,除了不可变对象以外,对于这些对象,您应该将 == 重写为值相等性。

所以,假设这里适用于指南,请选择语义上合理的方法。如果您正在处理不可变对象,并且期望两种方法产生相同的结果,我建议使用 == 以获得清晰度。


4
微软的指南并没有涉及到空值的问题,这正是该问题所涉及的。最重要的是,虽然 "myString == null" 是一个安全的测试,但 "myString.Equals(null)" 在 myString 为空时会导致异常。此外,微软的指南甚至没有提到 Object.Equals(myObject, b) 和 myObject.Equals(b) 之间的区别。前者是健壮的;后者在 myObject 为空时会引发异常。因此,虽然您提供的链接很有用,但它并不是对贴子提出的问题的回答。 - ToolmakerSteve

1
关于"...编写能够处理作者无法控制的对象的代码...",我想指出静态的Object.Equals==运算符都是静态方法,因此不能被虚拟/重写。哪个实现被调用是在编译时基于静态类型(s)确定的。换句话说,外部库无法为您编译后的代码提供不同版本的例程。

1
同样的,静态方法 object.Equals(a,b) 也是如此,它执行类似于 ((object)a == (object)b) || (a != null && b != null && a.Equals(b)) 的操作,也就是说,它可能会调用对象 a 上的虚拟 Equals 方法。 - LukeH
@LBushkin 您的观点是正确的,但这并不意味着我的陈述“并非完全正确”。 - Daniel Pratt
1
我同意你的观点,直到这句话:“外部库无法为编译后的代码提供不同版本的例程”。正如我在评论中提到的,以及@LukeH在他的评论中提到的,虚方法有可能作为“Object.Equals(a,b)”或“a == b”的结果而运行。 - LBushkin
@Daniel +1 谢谢。这是微软设计中非常微妙的错误。我刚刚验证了,当编译时类型更为通用时,“==”无法调用我的子类版本。但请注意,“Object.Equals”是OBJECT类中的静态方法,如果存在,则可以成功调用mySubclass.Equals(Object other)方法。读者应该意识到,它可能不会直接调用IEquatable版本的mySubclass.Equals(MySubclass other),如果存在的话。因此,在适当的时候编写通用的(Object other)来调用特定的(MySubclass other)是必不可少的。 - ToolmakerSteve
2
如果一个类型重写了Equals(Object)方法,静态的Object.Equals(Object,Object)方法将会在其中一个对象不为空时调用该重写方法;无论其参数如何转换类型都可以工作。相比之下,即使一个类型重载了==运算符,它也不能保证在转换为Object时使用该重载。 - supercat
显示剩余2条评论

0
请将此视为两个空值无法进行比较的原因。
(a == null ? "NA" : a).equals(b == null ? "NA" : b)

-1

我在尝试比较可能为空的对象的唯一标识符时,最终来到了这里。发现先填充缺失数据,然后再进行比较更容易。

Guid currentId = (Object1 == null) ? Guid.Empty : Object1.Id;
Guid newId = (Object2 == null) ? Guid.Empty : Object2.Id;
If (currentId == newId)
{
    //do happyface
}
else
{
   //do sadface
}

1
这不是对楼主问题的回答。 - jwdonahue
这个“答案”与当前讨论无关。 - ToolmakerSteve

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