等于(item,null)或item == null

时间:2010-08-17 22:11:45

标签: c# null equals robustness

使用static Object.Equals检查null的代码是否比使用==运算符或regular Object.Equals的代码更健壮?后两者是否容易被覆盖,以至于检查null不能按预期工作(例如,当比较值 为null时返回false)?

换句话说,是这样的:

if (Equals(item, null)) { /* Do Something */ }

比这更强大:

if (item == null) { /* Do Something */ }

我个人觉得后面的语法更容易阅读。编写处理作者控制之外的对象的代码(例如库)时是否应该避免?是否应始终避免(检查为空时)?这只是头发分裂吗?

6 个答案:

答案 0 :(得分:56)

这个问题没有简单的答案。在我看来,任何一个人总是使用其中一个的建议给你的建议很差。

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

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

这些都可以做不同的事情!

Object.Equals(a,b) (默认情况下)将对引用类型执行引用相等性比较,并对值类型执行按位比较。从MSDN文档:

  

Equals的默认实现   支持引用相等   引用类型和按位相等   对于价值类型。参考平等   表示对象引用   比较指的是同一个对象。   按位相等意味着对象   被比较的具有相同的二进制   表示。

     

请注意,派生类型可能   将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可以运行用户代码。请注意,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

可能有所不同。

让我们在这里注入一些实用主义。到目前为止,我们已经讨论过不同比较模式产生不同结果的可能性。虽然情况肯定如此,但有些类型可以安全地编写a == null。像StringNullable<T>这样的内置.NET类具有明确定义的语义以供比较。此外,它们是sealed - 通过继承阻止对其行为的任何更改。以下是非常常见的(也是正确的):

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

写作是不必要的(而且很丑陋):

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

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

答案 1 :(得分:5)

if (Equals(item, null))并不比if (item == null)强大,我发现它更难以启动。

答案 2 :(得分:2)

framework guidelines建议您将Equals视为值相等(检查两个对象是否表示相同的信息,即比较属性),并将==视为参考相等,不可变对象的异常,您可能应该将==覆盖为值相等。

因此,假设指南适用于此处,请选择语义上合理的指标。如果您正在处理不可变对象,并且您希望两种方法都能产生相同的结果,那么为了清晰起见,我会使用==

答案 3 :(得分:2)

如果您想测试IDENTITY(内存中的相同位置):

  

ReferenceEquals(a, b)

处理空值。而且不是可以覆盖的。 100%安全。

但请确保您确实需要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)”。

  • IF 你在 a已知不为空的逻辑内部,那么你可以使用更具可读性的“a.Equals(b)”。例如,“this.Equals(b)”是安全的。或者,如果“a”是在构造时初始化的字段,并且构造函数抛出异常,如果将null作为要在该字段中使用的值传入。

现在,要解决原来的问题:

  

问:这些是否容易在某些类中被覆盖,代码无法正确处理null,从而导致异常?

答:是的。获得100%安全EQUALITY测试的唯一方法是自己预先测试零值。

但是你呢?错误就在于(假设的未来坏类),这将是一种直接的失败类型。易于调试和修复(无论谁提供课程)。我怀疑这是一个经常发生的问题,或者在它确实发生时会持续很长时间。

更详细的答案:Object.Equals(a, b)最有可能面对写得不好的课程。如果“a”为null,则Object类将自己处理它,因此没有风险。如果“b”为空,则DYNAMIC(运行时非编译时)类型“a”确定调用“Equals”方法。当“b”为空时,被调用的方法只需要正常工作。除非被调用的方法编写得非常糟糕,否则它所做的第一步是确定“b”是否是它理解的类型。

所以Object.Equals(a, b)是可读性/编码工作与安全性之间的合理折衷。

答案 4 :(得分:1)

在参考“...编写将处理作者控制之外的对象的代码......”时,我要指出静态Object.Equals==运算符都是静态方法,因此无法虚拟/覆盖。根据静态类型确定在编译时确定调用哪个实现。换句话说,外部库无法为编译的代码提供不同版本的例程。

答案 5 :(得分:0)

当我尝试比较本身可能为null的对象的唯一ID时,我到了这里。发现更容易先估算缺失的数据,然后进行比较。

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