这个问题没有简单的答案。在我看来,任何说总是使用其中一种的人都会给你糟糕的建议。
实际上,您可以调用几种不同的方法来比较对象实例。给定两个对象实例a
和b
,您可以编写:
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 类喜欢String
并Nullable<T>
具有明确定义的语义以供比较。此外,它们正在sealed
- 通过继承防止对其行为进行任何更改。以下是很常见的(并且是正确的):
string s = ...
if( s == null ) { ... }
写是不必要的(而且丑陋的):
if( ReferenceEquals(s,null) ) { ... }
因此,在某些有限的情况下,使用==
是安全且适当的。