C#提供了四种判断相等性的方法:
先来考虑前两个静态方法。
ReferenceEquals()方法比较传入的两个参数是否引用了同一个对象,并不考虑内容是否相同。需要注意的是如果用这个方法比较值类型,因为对值类型进行了装箱,它将总是返回false,即使是和它自身比较。
Equals()方法则是调用objA.Equals(objB)来判断是否相等。
显然不用重写这两个方法,它们会按预期工作。
如果要自定义对象的相等性,则应重写实例方法Equals()。
在讨论如何重写这个方法之前,我要先说一下相等性应具有的数学性质。
自反性:a=a一定成立;
对称性:若a=b,则一定有b=a;
传递性:若a=b且b=c,则一定有a=c。
在自定义对象的的相等性时,一定要确保符合这三个性质。
现在可以开始讲重点了。
仅当Equals()的默认行为不能满足需求时,才重写这个方法。
Equals()的默认行为和ReferenceEquals()一样。但注意,ValueType重写了这个方法,它比较值类型的所有字段是否相等。
看起来值类型的默认Equals()方法很好用,但是,因为不知道被比较的对象的运行时类型,它是使用反射来实现的,这意味着性能损失。所以,定义值类型时总是应重写Equals()方法。
对于引用类型,仅当你想改变默认的基于引用的相等性时才重定义Equals()。许多BCL中的类都使用了基于值的相等性,例如,两个string对象比较的是它们是否包含了相同的内容。
由于Equals()方法使用了object类型的参数,对值类型进行比较时会进行大量的装箱和拆箱,一般情况下会同时实现IEquatable<T>接口的Equals(T other)。下面是标准的重写Equals()的方式。
注意,Equals()方法不应该抛出异常,如果传入了空引用或类型不符合,应该返回false。
另外如果重写了Equals()方法,同时也应该重写GetHashCode()方法,它将在Item 7中被讨论。
剩下的==操作符就很简单了,定义值类型时,总是应该重写它,同样是因为默认版本使用了反射。对于引用类型,一般不应重写它,BCL中的类预期所有引用类型的==操作符进行基于引用的比较。
最后要提一下IStructuralEquatable<T>接口(《Effective C#》中写的是IStructuralEquality,应该是作者搞错了)中的Equals(),它们比较两个对象在结构上是否相等(一般是集合对象),BCL中的Array和Tuple<>都实现了这个接口。