当前位置: 首页 > news >正文

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

C# - 为值类型重定义相等性
为什么要为值类型重定义相等性
原因主要有以下几点:

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

实现步骤
重写object.Equals()方法
实现IEquatable.Equals()接口方法
重写 == 和 != 操作符
重写object.GetHashCode()
具体来说:

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

这里用到了反射。

而实现IEquatable.Equals()接口方法,可以避免装箱,并且保证类型安全。

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

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

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

实现
先看实例struct:

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

实现IEquatable接口
首先来实现IEquatable接口。

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

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

重写object.Equals()方法

这个代码是resharper生成的。

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

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

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

实现 == 和 != 操作符

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

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

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

而类型里拥有GetHashCode()方法(返回Hash)的真正目的是,允许该类型在内部使用HashTable的集合中可以作为Key,因为HashTable需要这些哈希码。例如Dictionary。

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

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

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

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

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

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

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

检验

结果如预期,OK。

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

最后再重复一次,为值类型定义相等性一定要实现上述4各步骤的5个方法。
原文地址https://www.cnblogs.com/cgzl/p/10699667.html

相关文章:

  • Es6初级入门(一)
  • thinkphp+redis实现秒杀,缓存等功能
  • (JS基础)String 类型
  • django2中表单的使用二
  • css学习_css布局案例
  • jsp简单介绍
  • python inspect模块
  • 通过SQL脚本来查询SQLServer 中主外键关系
  • python的pandas库学习笔记
  • 最全的前端模块化方案
  • 深入浅出了解“装箱与拆箱”
  • 力扣算法题—091解码
  • 如何利用在线画图网站绘制流程图
  • 软件原型设计工具Axure
  • 数据脱敏(一)--基础知识
  • 「前端早读君006」移动开发必备:那些玩转H5的小技巧
  • 【css3】浏览器内核及其兼容性
  • AWS实战 - 利用IAM对S3做访问控制
  • ERLANG 网工修炼笔记 ---- UDP
  • JavaScript 是如何工作的:WebRTC 和对等网络的机制!
  • JavaScript/HTML5图表开发工具JavaScript Charts v3.19.6发布【附下载】
  • JS学习笔记——闭包
  • Mithril.js 入门介绍
  • node学习系列之简单文件上传
  • ⭐ Unity 开发bug —— 打包后shader失效或者bug (我这里用Shader做两张图片的合并发现了问题)
  • 番外篇1:在Windows环境下安装JDK
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 基于webpack 的 vue 多页架构
  • 检测对象或数组
  • 理解 C# 泛型接口中的协变与逆变(抗变)
  • 那些被忽略的 JavaScript 数组方法细节
  • 前端代码风格自动化系列(二)之Commitlint
  • 思否第一天
  • 思维导图—你不知道的JavaScript中卷
  • 想晋级高级工程师只知道表面是不够的!Git内部原理介绍
  • 容器镜像
  • ​​​​​​​​​​​​​​汽车网络信息安全分析方法论
  • ​DB-Engines 11月数据库排名:PostgreSQL坐稳同期涨幅榜冠军宝座
  • ​人工智能书单(数学基础篇)
  • #13 yum、编译安装与sed命令的使用
  • #define,static,const,三种常量的区别
  • (function(){})()的分步解析
  • (十二)springboot实战——SSE服务推送事件案例实现
  • (十六)Flask之蓝图
  • (未解决)macOS matplotlib 中文是方框
  • (原)记一次CentOS7 磁盘空间大小异常的解决过程
  • (转) Face-Resources
  • (转)总结使用Unity 3D优化游戏运行性能的经验
  • .mkp勒索病毒解密方法|勒索病毒解决|勒索病毒恢复|数据库修复
  • .net 7 上传文件踩坑
  • .NET Core 中插件式开发实现
  • .net6+aspose.words导出word并转pdf
  • .NET开源全面方便的第三方登录组件集合 - MrHuo.OAuth
  • .pings勒索病毒的威胁:如何应对.pings勒索病毒的突袭?
  • @KafkaListener注解详解(一)| 常用参数详解