欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-04 13:27:09
为什么要为值类型重定义相等性 原因主要有以下几点: 值类型默认无法使用 == 操作符,除非对它进行重写 再就是性能原因,因为值类型默认的相等性比较会使用装箱和反射,所以性能很差 根据业务需求,其实际相等性的意义和默认的比较结果可能会不同,但是这种情况可能不较少 所以建议是:所有供外部使用的struc ......

为什么要为值类型重定义相等性

原因主要有以下几点:

  • 值类型默认无法使用 == 操作符,除非对它进行重写
  • 再就是性能原因,因为值类型默认的相等性比较会使用装箱和反射,所以性能很差
  • 根据业务需求,其实际相等性的意义和默认的比较结果可能会不同,但是这种情况可能不较少

所以建议是:所有供外部使用的struct都实现相等性。

 

实现步骤

  • 重写object.equals()方法
  • 实现iequatable<t>.equals()接口方法
  • 重写 == 和 != 操作符
  • 重写object.gethashcode()

具体来说:

重写object.equals()方法,是避免了反射,因为system.valuetype里面对object.equals()方法的重写实现如下:

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

这里用到了反射。

而实现iequatable<t>.equals()接口方法,可以避免装箱,并且保证类型安全。

而实现==和!=,也就允许值类型使用该操作符了,写起来更方便直观,易于理解。而且这两个操作符必须一同实现。

而重写object.gethashcode(),则是一个最佳实践。

 

所有为值类型重定义相等性,一共分4步,每步都是必须的

 

实现

先看实例struct:

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

有构造函数,涉及到一个enum,并重写了tostring()方法。

 

实现iequatable<t>接口

首先来实现iequatable<t>接口。

(如果你使用resharper或者rider,那么实现该接口的时候它会自动把object的equals和gethashcode方法都重写了,并且自动完成了有意义的代码)

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

这里面我对三个属性进行了比较,使用了==操作符。其中==对于string来说就是比较值,而enum其实就是int,datetime也是值类型,并且已经实现了相等性判断的功能。

 

重写object.equals()方法

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

这个代码是resharper生成的。

代码很简单,首先检查是否为null,然后检查这个object是不是一个person,这里使用了 is 操作符,并把它转型为person,赋给了一个叫做other的变量。最后调用的这个equals()方法,是我们上面写的那个强类型的方法,因为other变量的类型是person。

但是这个方法仍然涉及到装箱操作,所以还是iequatable<t>的实现方法更快一些。

 

如果只重写了object.equals()方法,而没有重写gethascode()方法,那么resharper会有提示:

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

 

实现 == 和 != 操作符

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

这个很简单,直接调用强类型的equals()方法即可,而且由于person是值类型,所以不用检查null,值类型不会为null。

 

如果只实现了其中一个操作符,那么会报错的。

 

实现object.gethashcode()

gethashcode()这个方法会返回一个32位的哈希码,它代表着对象内容的哈希值。

而类型里拥有gethashcode()方法(返回hash)的真正目的是,允许该类型在内部使用hashtable的集合中可以作为key,因为hashtable需要这些哈希码。例如dictionary<tk, tv>。

为了让hashtable可以正确的工作,hash码有一个要求:如果两个实例被认为是相等的,那么它们必须返回相同的hash码。如果没有实现这个要求,那么你可能会发现这个类型作为dictionary的key的时候,会有一些意想不到的结果。

所以如果重写了object.equals()方法,那么就得重写object.gethashcode()方法。

 

看一下resharper自动实现的代码:

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

这里使用了unchecked,防止抛出溢出异常。

name是引用类型,可能为null,所以判断一下。

然后其它两个int和datetime类型,微软都做好了其gethashcode()的实现。

这里对它们进行异或操作。之所以使用397这个数,可能因为397是一个足够大的质数,可以导致溢出,并混淆各位,之所以使用质数,是因为用质数相乘会得到比用其他任意数相乘更均匀的结果。

 

检验

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

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

结果如预期,ok。

 

总结

在这几个动作里,实际的逻辑写在了iequatable<t>.equals()方法里,object.equals()就是检查类型然后调用iequatable<t>.equals(),== 和 != 操作符也是调用iequatable<t>.equals(),而gethashcode()则使用了按位异或。

 

最后再重复一次,为值类型定义相等性一定要实现上述4各步骤的5个方法