在 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)
{
}

本文所有代码放在 githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 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 推荐只取只读属性或字段做哈希值的更多相关文章

  1. MySQL中in('5,6,7')只取第一个id为5对应的数据的思考

    通过阅读本文你可以更好的理解两个知识点: 1.#{}与${}在实际项目中的使用,避免在项目中使用不当造成不可预知的Bug; 2.MySQL中in里面如果是字符串的话,为什么只取第一个对应的数据,eg: ...

  2. Sql中时间只取年或者年月

    select Title,datepart(year,DateCreated) from CMS_Content    只取年 只显示年月,不显示日:select datepart(year,getd ...

  3. SQL排除重复结果只取字段最大值

    如何用SQL排除重复结果只取字段最大值的记录?要求得到的结果(即是PID相同的记录只取ID值最大的那一条). select * from [Sheet1$] a from [Sheet1$] wher ...

  4. Oracle初级入门 根据某字段重复只取一条记录,并计计算重复条数

    在平常开发中,去重复数据经常使用到,本人新手,接触Oracle也不久,开发中用到的小知识点,记录一下,老鸟可绕道,如果有写错的,请指正. 去重复记录可以使用distinct,当只查询一列数据时,可以轻 ...

  5. Python: re.compile最短匹配模式,只取双引号内的值\“

    用正则表达式匹配某个文本模式 1.只取双引号内的值 2.长短匹配模式对比 贪婪模式:     模式r'\"(.*)\" '的意图是匹配被双引号包含的文本,但是这个表达式中*是贪婪的 ...

  6. Mysql,重复字段只取其中一行

    Mysql,重复字段只取其中一行 格式 : select 字段 from [表] where 其他字段 in (select 函数(其他字段) from [表] group by 相同字段) 示例如下 ...

  7. 遍历select搜索结果,只取数字标key值,防止重复

    //遍历select搜索结果,只取数字标key值,防止重复 foreach ($row as $key => $value) { if (is_int($key)) { echo $value; ...

  8. string反向找位置,分割字符串(只取文件夹路径)

    1 #include <uf.h> 2 #include <uf_part.h> 3 #include <atlstr.h> 4 #include <iost ...

  9. AngularJS filter:search 是如何匹配的 ng-repeat filter:search ,filter:{$:search},只取repeat的item的value 不含label

    1.  filter可以接收参数,参数用 : 进行分割,如下: {{ expression | filter:argument1:argument2:... }} 2.   filter参数是 对象 ...

  10. 利用Python爬虫爬取淘宝商品做数据挖掘分析实战篇,超详细教程

    项目内容 本案例选择>> 商品类目:沙发: 数量:共100页  4400个商品: 筛选条件:天猫.销量从高到低.价格500元以上. 项目目的 1. 对商品标题进行文本分析 词云可视化 2. ...

随机推荐

  1. Android组件化开发实践和案例分享

    目录介绍 1.为什么要组件化 1.1 为什么要组件化 1.2 现阶段遇到的问题 2.组件化的概念 2.1 什么是组件化 2.2 区分模块化与组件化 2.3 组件化优势好处 2.4 区分组件化和插件化 ...

  2. IValueConverter的基础用法

    1.我们在做工控项目的时候通常设置配方的上下限 这个时候要求OK数在上下限范围之内,否则NG 首先我们绑定一个简单的List用来展示数据,我这里用学生Age来展示 <ListView Items ...

  3. 这里有你不得不了解的Java 11版本特性说明

    「MoreThanJava」 宣扬的是 「学习,不止 CODE」,本系列 Java 基础教程是自己在结合各方面的知识之后,对 Java 基础的一个总回顾,旨在 「帮助新朋友快速高质量的学习」. 当然 ...

  4. elasticsearch中runtime_mapping实战

    背景:需要根据一个实时计算处理的结果值进行排序,数据从es中查询.(基于业务背景:佣金排序) es版本:7.17.1:spring-data-elasticsearch版本:4.3.9 方式一:mys ...

  5. clickhouse在各大厂商的应用

    案例-ClickHouse在头条的技术演进

  6. [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 ...

  7. ZYNQ7000系列学习之TF卡读写(2)

    ZYNQ读写实验(2) 1.实验原理 在TF卡读写实验1中,已经将每一个步骤都做完了,但是最后得到的结果是错误的.那个时候由于TF没有格式化,显示的是错误信息.在格式化后,再次实验,得到了预期的结果. ...

  8. 连接Windows 平台 KingbaseES异常

    概述 应用连接Windows平台的KingbaseES 数据库,报错"com.kingbase8.util.KSQLException: 致命错误: 用户"system" ...

  9. UE4 c++重构简单死亡之眼的效果

    虚幻社区中有蓝图教学视频 使用C++重构,主要用到UGameplayStatics类中的SetGlobalTimerDilation方法,以及角色的相机管理器的调用,之后通过StartCameraFa ...

  10. CentOS 7 上搭建nginx来部署静态网页

    目录 0. Nginx简介 1. 安装以及使用 1.1 安装和启动 1.2 配置服务器的访问地址 1.3 重启nginx,打开浏览器访问 0. Nginx简介 Nginx (engine x) 是一个 ...