这个问题没有简单的答案。在我看来,任何说总是使用其中一种的人都会给你糟糕的建议。
实际上,您可以调用几种不同的方法来比较对象实例。给定两个对象实例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) ) { ... }
因此,在某些有限的情况下,使用==是安全且适当的。