通常情况下引用类型的相等性是不应该被重定义/重写的。
例如两个引用类型的变量 x 和 y,如果这样写:if(x == y) {...},那么大家都明白,这个比较的是引用的相等性。
但是有少数情况下,也可以为引用类型重写相等性。
例如这个类:
这个类里面只有两个string类型的属性和字段,那么对它的相等性来说,更合理的是去比较值,而不是引用。
还有一种情况,就是表示数学的引用类型。
例如有一个类表示矩阵 Matrix,那么这样写 if(matrix1 == matrix2) {...} 更适合表示它们两个的值相等。
上述的这两个例子其实也不是十分的必要。所以想为引用类型重写相等性的时候还是应该先想好,重写后是否能够更加的直观,使理解便得更简单了。
实际上如果想比较两个应用类型里面的值是否相等,你不必非得去重写那些相等性的方法,你可以通过实现IEqualityComparer<T>接口来写一个单独的相等性比较器。但是这样的话不能使用==操作符,需要这样写:if(eqComparer.Equals(x, y)) {...}
为引用类型重写相等性
一个类:
首先重写object.Equals()方法:
这个逻辑比较简单,就是判断null,引用和类型,然后再判断各个属性(字段)的值是否相等。
然后还需要重写object.GetHashCode()方法:
这个采用了Resharper生成的方法,以前说过,就不再介绍了。
最佳实践还要求重写C#的==操作符:
当然配套的!=也必须重写。
在之前重写值类型相等性的文章里,我还为值类型实现了IEquatable<T>接口,而对于引用类型来说,就没有必要去实现该接口了,可以把相等性判断逻辑放在object.Equals()方法里。
派生类
这是上面Citizen类的一个子类:
下面我重写object.Equals() 方法:
大部分逻辑都在base.Equals()方法里了,首先如果父类的Equals()方法返回false,那么下面也就不用做啥了。但是如果父类Equals()认为这两个实例是相等的,这就意味着父类里所有的相等性检查都通过了,然后我们仍然需要检查派生类里面的独有字段(属性),而这个例子里只有一个字段(属性)。
然后别忘了实现GetHashCode()方法:
(resharper生成的代码)
这个方法里使用了父类的GetHashCode()方法,把它按位异或IdCard的GetHashCode()的结果。
然后实现==和!=操作符:
好,现在我们来测试一下:
其结果如下:
这个结果还都是对值进行比较的,符合预期。
然后你可能以为这样实现没有问题了。。。。
陷阱
现在我在Citizen这个父类里修改一下==的实现,我想让它更有效率:
然后我再执行和上面同样的测试代码,其结果输入是: