C# - 为引用类型重定义相等性

时间:2021-09-19 09:56:52

通常情况下引用类型的相等性是不应该被重定义/重写的。

例如两个引用类型的变量 x 和 y,如果这样写:if(x == y) {...},那么大家都明白,这个比较的是引用的相等性。

但是有少数情况下,也可以为引用类型重写相等性。

例如这个类:

C# - 为引用类型重定义相等性

这个类里面只有两个string类型的属性和字段,那么对它的相等性来说,更合理的是去比较值,而不是引用。

还有一种情况,就是表示数学的引用类型。

例如有一个类表示矩阵 Matrix,那么这样写 if(matrix1 == matrix2) {...} 更适合表示它们两个的值相等。

上述的这两个例子其实也不是十分的必要。所以想为引用类型重写相等性的时候还是应该先想好,重写后是否能够更加的直观,使理解便得更简单了。

实际上如果想比较两个应用类型里面的值是否相等,你不必非得去重写那些相等性的方法,你可以通过实现IEqualityComparer<T>接口来写一个单独的相等性比较器。但是这样的话不能使用==操作符,需要这样写:if(eqComparer.Equals(x, y)) {...}

为引用类型重写相等性

一个类:

C# - 为引用类型重定义相等性

首先重写object.Equals()方法:

C# - 为引用类型重定义相等性

这个逻辑比较简单,就是判断null,引用和类型,然后再判断各个属性(字段)的值是否相等。

然后还需要重写object.GetHashCode()方法:

C# - 为引用类型重定义相等性

这个采用了Resharper生成的方法,以前说过,就不再介绍了。

最佳实践还要求重写C#的==操作符:

C# - 为引用类型重定义相等性

当然配套的!=也必须重写。

在之前重写值类型相等性的文章里,我还为值类型实现了IEquatable<T>接口,而对于引用类型来说,就没有必要去实现该接口了,可以把相等性判断逻辑放在object.Equals()方法里。

派生类

这是上面Citizen类的一个子类:

C# - 为引用类型重定义相等性

下面我重写object.Equals() 方法:

C# - 为引用类型重定义相等性

大部分逻辑都在base.Equals()方法里了,首先如果父类的Equals()方法返回false,那么下面也就不用做啥了。但是如果父类Equals()认为这两个实例是相等的,这就意味着父类里所有的相等性检查都通过了,然后我们仍然需要检查派生类里面的独有字段(属性),而这个例子里只有一个字段(属性)。

然后别忘了实现GetHashCode()方法:

C# - 为引用类型重定义相等性

(resharper生成的代码)

这个方法里使用了父类的GetHashCode()方法,把它按位异或IdCard的GetHashCode()的结果。

然后实现==和!=操作符:

C# - 为引用类型重定义相等性

好,现在我们来测试一下:

C# - 为引用类型重定义相等性

其结果如下:

C# - 为引用类型重定义相等性

这个结果还都是对值进行比较的,符合预期。

然后你可能以为这样实现没有问题了。。。。

陷阱

现在我在Citizen这个父类里修改一下==的实现,我想让它更有效率:

C# - 为引用类型重定义相等性

然后我再执行和上面同样的测试代码,其结果输入是:

C# - 为引用类型重定义相等性