dotnet C# 基础 为什么 GetHashCode 推荐只取只读属性或字段做哈希值
在 C# 里面,所有的对象都继承 Object 类型,此类型有开放 GetHashCode 用于给开发者重写。此 GetHashCode 方法推荐是在重写 Equals 方法时也同时进行重写,要求两个对象在 Equals 返回相等时,两个对象的 GetHashCode 返回值也相等。反过来则不然,允许有两个不相等的对象的 GetHashCode 是相等的
在重写 Equals 方法时,大部分时候都是自动生成的,如将类里面的所有字段或属性都进行一一比较。那在 GetHashCode 方法里面,所输出的哈希值的计算,是否也需要使用此类型的所有字段或属性共同计算出来?如果在 GetHashCode 里面使用的字段或属性非只读,那么 ReSharper 将会警告你这是不安全的。本文将来告诉大家为什么这是不安全的
在 dotnet 里面,大部分会用到 GetHashCode 的逻辑都在于哈希容器里面,如 Dictionary 字典等。这些哈希容器在设计上都期望类型遵守以下行为:当两个对象相等的时候,那么获取 GetHashCode 的值也一定相等
假定有类型的 GetHashCode 返回值是基于非只读的属性或字段,将会导致在将对象加入哈希容器的时候,所获取到的 GetHashCode 的值是不包括未来对非只读属性或字段变更的防御的。在未来对此对象的非只读的属性或字段进行变更,也许就会影响到此对象再次获取 GetHashCode 的属性,从而让相同的一个对象,在哈希容器里面,因为 GetHashCode 返回值不同,而被认为是不同的对象
假设有如此的代码逻辑,某个 Foo2 的对象的 GetHashCode 返回值是由此对象的属性决定的,如下面代码
class Foo2
{
public int HashCode { set; get; }
public override int GetHashCode()
{
return HashCode;
}
}
假定将此 Foo2 的对象加入到字典里面,接着去判断字典里面是否存在此对象。再修改 Foo2 的 HashCode 属性,再去判断字典里面是否存在此对象,代码如下
var foo2 = new Foo2();
Dictionary<Foo2, object> dictionary = new();
dictionary[foo2] = foo2;
Console.WriteLine(dictionary.ContainsKey(foo2));
foo2.HashCode = 2;
Console.WriteLine(dictionary.ContainsKey(foo2));
有趣的逻辑是第一次返回的符合预期,就是 True 的值。然而第二次,明明没有从字典里面移除 Foo2 对象,然而字典却认为找不到此对象
其原因如上文,在字典里面,优先通过 GetHashCode 的值来进行判断。如上面代码,更改了 Foo2 的 GetHashCode 返回值,将会让字典找不到此 HashCode 对应的元素,从而让字典认为不存在此对象
大部分在设计类型的时候,都不会考虑到某个类型在未来或其他模块里面,会被存放进哈希容器里面。如果此时在 GetHashCode 里面,使用了非只读字段或属性,将会挖一个坑。也许某个逻辑变更了这些非只读字段或属性的时候,影响了 GetHashCode 的返回值从而影响了哈希容器的行为
这就是为什么 ReSharper 警告不要在 GetHashCode 里面使用非只读字段或属性进行制作哈希值的原因
不过在理解了这个行为,在某些特别的业务里面,也可以利用此行为实现有趣的功能
通过本文也可以了解到,对于 GetHashCode 的返回值也不能为了因为重写 Equals 方法而被 VS 警告而随便写此方法的实现,如下面逗比代码
class Foo
{
public Foo(string name)
{
Name = name;
}
public string Name { get; }
public override int GetHashCode()
{
return _random.Next();
}
private readonly Random _random = new Random();
}
上面的代码在 GetHashCode 随机返回一个值,这将会让所有哈希容器炸掉,如下面的代码,将在字典拿不到值
try
{
Foo[] foo = new Foo[100];
for (int i = 0; i < 100; i++)
{
foo[i] = new Foo(i.ToString());
}
Dictionary<Foo, string> dictionary = foo.ToDictionary(t => t, t => t.Name);
for (int i = 0; i < foo.Length; i++)
{
Console.WriteLine($"{foo[i].Name}-{dictionary[foo[i]]}"); // KeyNotFoundException
}
}
catch (Exception)
{
}
可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 572e405d383c69929397a583102576e2e140f1fc
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
获取代码之后,进入 BelwheaheajeachelYikaidairnay 文件夹
dotnet C# 基础 为什么 GetHashCode 推荐只取只读属性或字段做哈希值的更多相关文章
- MySQL中in('5,6,7')只取第一个id为5对应的数据的思考
通过阅读本文你可以更好的理解两个知识点: 1.#{}与${}在实际项目中的使用,避免在项目中使用不当造成不可预知的Bug; 2.MySQL中in里面如果是字符串的话,为什么只取第一个对应的数据,eg: ...
- Sql中时间只取年或者年月
select Title,datepart(year,DateCreated) from CMS_Content 只取年 只显示年月,不显示日:select datepart(year,getd ...
- SQL排除重复结果只取字段最大值
如何用SQL排除重复结果只取字段最大值的记录?要求得到的结果(即是PID相同的记录只取ID值最大的那一条). select * from [Sheet1$] a from [Sheet1$] wher ...
- Oracle初级入门 根据某字段重复只取一条记录,并计计算重复条数
在平常开发中,去重复数据经常使用到,本人新手,接触Oracle也不久,开发中用到的小知识点,记录一下,老鸟可绕道,如果有写错的,请指正. 去重复记录可以使用distinct,当只查询一列数据时,可以轻 ...
- Python: re.compile最短匹配模式,只取双引号内的值\“
用正则表达式匹配某个文本模式 1.只取双引号内的值 2.长短匹配模式对比 贪婪模式: 模式r'\"(.*)\" '的意图是匹配被双引号包含的文本,但是这个表达式中*是贪婪的 ...
- Mysql,重复字段只取其中一行
Mysql,重复字段只取其中一行 格式 : select 字段 from [表] where 其他字段 in (select 函数(其他字段) from [表] group by 相同字段) 示例如下 ...
- 遍历select搜索结果,只取数字标key值,防止重复
//遍历select搜索结果,只取数字标key值,防止重复 foreach ($row as $key => $value) { if (is_int($key)) { echo $value; ...
- string反向找位置,分割字符串(只取文件夹路径)
1 #include <uf.h> 2 #include <uf_part.h> 3 #include <atlstr.h> 4 #include <iost ...
- AngularJS filter:search 是如何匹配的 ng-repeat filter:search ,filter:{$:search},只取repeat的item的value 不含label
1. filter可以接收参数,参数用 : 进行分割,如下: {{ expression | filter:argument1:argument2:... }} 2. filter参数是 对象 ...
- 利用Python爬虫爬取淘宝商品做数据挖掘分析实战篇,超详细教程
项目内容 本案例选择>> 商品类目:沙发: 数量:共100页 4400个商品: 筛选条件:天猫.销量从高到低.价格500元以上. 项目目的 1. 对商品标题进行文本分析 词云可视化 2. ...
随机推荐
- Android组件化开发实践和案例分享
目录介绍 1.为什么要组件化 1.1 为什么要组件化 1.2 现阶段遇到的问题 2.组件化的概念 2.1 什么是组件化 2.2 区分模块化与组件化 2.3 组件化优势好处 2.4 区分组件化和插件化 ...
- IValueConverter的基础用法
1.我们在做工控项目的时候通常设置配方的上下限 这个时候要求OK数在上下限范围之内,否则NG 首先我们绑定一个简单的List用来展示数据,我这里用学生Age来展示 <ListView Items ...
- 这里有你不得不了解的Java 11版本特性说明
「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」. 当然 ...
- elasticsearch中runtime_mapping实战
背景:需要根据一个实时计算处理的结果值进行排序,数据从es中查询.(基于业务背景:佣金排序) es版本:7.17.1:spring-data-elasticsearch版本:4.3.9 方式一:mys ...
- clickhouse在各大厂商的应用
案例-ClickHouse在头条的技术演进
- [ERROR] “不支持使用 SOAP 编码。SOAP 扩展元素包含 use=“encoded“ “ 无法解析 WSDL。
下载axis-1_4,地址https://archive.apache.org/dist/ws/axis/1_4/ 解压,进入D:\axis-1_4\lib 执行命令 java -cp mail.ja ...
- ZYNQ7000系列学习之TF卡读写(2)
ZYNQ读写实验(2) 1.实验原理 在TF卡读写实验1中,已经将每一个步骤都做完了,但是最后得到的结果是错误的.那个时候由于TF没有格式化,显示的是错误信息.在格式化后,再次实验,得到了预期的结果. ...
- 连接Windows 平台 KingbaseES异常
概述 应用连接Windows平台的KingbaseES 数据库,报错"com.kingbase8.util.KSQLException: 致命错误: 用户"system" ...
- UE4 c++重构简单死亡之眼的效果
虚幻社区中有蓝图教学视频 使用C++重构,主要用到UGameplayStatics类中的SetGlobalTimerDilation方法,以及角色的相机管理器的调用,之后通过StartCameraFa ...
- CentOS 7 上搭建nginx来部署静态网页
目录 0. Nginx简介 1. 安装以及使用 1.1 安装和启动 1.2 配置服务器的访问地址 1.3 重启nginx,打开浏览器访问 0. Nginx简介 Nginx (engine x) 是一个 ...