47

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

换句话说,是这样的:

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

比这更强大:

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

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

4

6 回答 6

80

这个问题没有简单的答案。在我看来,任何说总是使用其中一种的人都会给你糟糕的建议。

实际上,您可以调用几种不同的方法来比较对象实例。给定两个对象实例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。如果该运算符的实现调用了a或上的实例方法b,它也可能取决于参数的运行时类型。由于调度基于表达式中的类型,因此以下可能会产生不同的结果:

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. 内置的 .NET 类喜欢StringNullable<T>具有明确定义的语义以供比较。此外,它们正在sealed- 通过继承防止对其行为进行任何更改。以下是很常见的(并且是正确的):

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

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

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

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

于 2010-08-17T22:29:21.410 回答
5

if (Equals(item, null))不比 更健壮if (item == null),而且我发现它更容易启动。

于 2010-08-17T22:13:40.433 回答
3

当您想测试 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)”。

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

现在,解决原来的问题:

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

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

但是你应该吗?错误将在那个(假设的未来坏类)中,这将是一种简单的失败类型。易于调试和修复(由提供课程的人)。我怀疑这是一个经常发生的问题,或者当它确实发生时会持续很长时间。

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

Object.Equals(a, b)可读性/coding_effort 和安全性之间的合理折衷也是如此。

于 2013-09-26T23:30:45.217 回答
2

框架指南建议您将其Equals视为值相等(检查两个对象是否表示相同的信息,即比较属性)和==引用相等,不可变对象除外,您可能应该将其覆盖==为值相等。

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

于 2010-08-17T22:16:08.197 回答
1

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

于 2010-08-17T22:26:28.970 回答
0

当我试图比较本身可能为空的对象的唯一 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
}
于 2020-10-29T03:48:47.493 回答