一、讨论的起因
先看如下的一段代码:
string s1 = "123"; string s2 = "123"; Console.WriteLine(object.ReferenceEquals(s1, s2)); Console.WriteLine(s1 == s2); Console.WriteLine((object) s1 == (object) s2);
运行结果:
True
True
True
第一个True:ReferenceEquals使用两个引用的内存地址进行比较。上例中声明了两个相同的字符串变量,编译器会将它们存储在同一位置,所以这两个字符串实际引用内存中的同一对象。这个没有争议。
第二个True:利用string对==的重载完成字符串比较,同样没有争议。
二、讨论的重点
争议的重点是第三个True。存在如下两种观点:
意见A: 这是类似于ReferenceEquals的比较,等价于前述第一个True,没有更多的细节。
意见B (我的观点): 基本同意意见A,但由于(object)s1==(object)s2 与 ((object)s1).Equals((object)s2)在语义上的相近,都可以理解为比较2个object是否相等,所以很容易掉进一个语义上的陷阱: ==比较不相等,Equals()比较能相等。
以下是我的一段模拟代码:
using System; namespace EqualsTest { class MainClass { public static void Main(string[] args) { SomeClass a = new SomeClass() { _name = "123" }; SomeClass b = new SomeClass() { _name = "123" }; Console.WriteLine(a == b); Console.WriteLine((object) a == (object) b); Console.WriteLine(((object) a).Equals((object) b)); } } class SomeClass : Object { public string _name = null; public override bool Equals(object obj) { Console.WriteLine("Call in SomeClass.Equals..."); return _name == ((SomeClass) obj)._name; } public static bool operator ==(SomeClass a, SomeClass b) { Console.WriteLine("Call in SomeClass..."); return a.Equals(b); } public static bool operator !=(SomeClass a, SomeClass b) { Console.WriteLine("Call in SomeClass..."); return !a.Equals(b); } } }
运行结果:
Call in SomeClass... Call in SomeClass.Equals... True False Call in SomeClass.Equals... True
在这段模拟代码里,==与Equals()不再等价。造成差别的原因,就在于SomeClass重写(Override)了Object的 Equals()方法,从而利用动态联编特性在运行时取代了Object.Equals()。而另一方面,操作符==是专属于类的static重载,因此无法在运行时被其子类取代。
附上Object.Equals()方法原型:
public virtual bool Equals(Object obj);
三、结论
为了避免类似这样的问题,应当避免在用object进行强制类型转换后使用==这样的比较,而应选择object.ReferenceEquals()。