《Effective C#》Item 10:小心GetHashCode函数所存在的陷阱

信创那些事儿——Spring Boot中集成东方通中间件(TongWeb) 在Spring Boot中集成东方通中间件(如TongWeb作为Servlet容器)通常涉及几个步骤,但需要注意的是,TongWeb本身是一个独立的Java EE应用服务器,而不是像Tomcat那样可以直接嵌入到Spring Boot应用中的中间件。因此,集成通常意味着将Spring Boot应用打包为WAR文件并部署到TongWeb服务器上。 阅读详情

GetHashCode函数,看了它的名字就知道它会被用在哪里。没错,这个函数一般是在操作HashTable或者Dictionary之类的数据集的时候被调用。每个类型,不管是值类型还是引用类型,都提供这个基本函数,同样也可以像重写ToString或者Equals函数一样去重写它。但是我这里要说的,不建议重写此函数,而且在使用这个函数也需要加倍小心。

 

Why? 有些人看了我所说的,会产生类似的疑问。我这里要提的一点就是,对于引用类型自带的GetHashCode函数来说,基本上是正确的,但是效率不高;而对于值类型自带的GetHashCode函数而言,基本上是不正确的,即使正确也是效率不高。如果重写类型的GetHashCode函数,想要达到既正确又高效是不可能的。

 

为了解开如上的疑问之前,先来说说实现一个类型的GetHashCode函数,需要满足那几点才能是合格的。显然GetHashCode的目的是产生一个Key,为了方便在HashTable或者Dictionary中的检索。既然是这样,那么对于GetHashCode来说,要满足如下三点,这也是判断一个GetHashCode函数是否有效的标准。

 

第一,两个相等的对象,通过GetHashCode函数产生的结果要相等,此外两个不相等的对象,通过GetHashCode函数的返回值要不相等;否则,通过其产生HashCode而存入HashTable中的数据就无法取出来了。

 

第二,对于一个类型的对象来说,其GetHashCode函数的返回值要自始至终要保持一致。否则,和第一点一样。

 

第三,在GetHashCode函数中需要提供一个比较好的哈希函数,也就是在最小的范围内来实现数据分散,换句话说它的离散度决定HashTable存取效率。

 

知道了GetHashCode函数的验证标准,接下来就来解开前面的疑团。

 

首先说说引用类型自带的GetHashCode函数实现。一个.Net程序在运行的时候会对引用类型的对象进行标记,大致操作类似如下:

标记起始为0,当创建一个引用类型对象的时候,这个标记会自动加一,对象释放后标记并不做减一操作,这有点儿像数据库中的自增字段。

 

那么对于引用类型的GetHashCode其实就是返回当前引用标记。这也就是为什么说引用类型的GetHashCode函数基本是正确的原因。

 

因为对于第一条来说,如果类型没有重载Equals或者Operator==函数的话,类型自带的Equals函数只是在对象引用层进行验证,也就是说,一个对象等于另外一个对象就说明这个对象要么是另外一个对象的引用,要么另外一个对象是这个对象的引用。这说明没有新的引用对象产生,那么当引用标记也不会发生变化,所以对于第一条来说满足(如果要是重载了Equals或者Operator==函数的话,那么相应要提供此版本的GetHashCode函数,这一点在后面进行叙说)。

 

至于第二条来说,由于对象数据成员发生改变不会影响到引用标记的改变,所以对于第二条来说也是满足的。

 

前两点的满足,说明了引用类型的GetHashCode是正确地。但是对于第三点来说,由于引用标记是相对于整个程序而言的,并不是类型所特有的,那么它的效率不高是不言而喻的。

 

那么对于值类型自带的GetHashCode函数呢,就更有趣了,为了更形象地说明它的有趣,请先参看如下的代码,猜猜Debug的输出是什么。

        public struct ErrorMessage

        {

            private string strMsg;

            private int nErrorCode;

            private DateTime dtInvoked;

 

            public ErrorMessage( string Msg, int ErrorCode )

            {

                strMsg = Msg;

                nErrorCode = ErrorCode;

                dtInvoked = DateTime.Now;

            }

 

            public bool TestHashCode()

            {

                return this.GetHashCode() == strMsg.GetHashCode();

            }

        }

 

        // Test "GetHashCode" function in value type

        ErrorMessage err = new ErrorMessage( "Test", 0 );

        if( err.TestHashCode() )

            Debug.WriteLine( "Both hash code equal!" );

        else

            Debug.WriteLine( "Not equal!" );

 

可能谁都没有想到,Debug中的输出是“Both hash code equal!”。为什么呢?原因很简单,值类型自带的GetHashCode是以其第一个成员的GetHashCode值作为其的返回值。

 

显然对于第一条来说,两个相等值类型对象,其的GetHashCode函数返回值是相等的,这没什么问题;但是对于不相等的两个对象来说,它们的GetHashCode返回值则有可能相等。显然违反了第一条。

 

其次对于第二条来说,由于值类型的GetHashCode返回值等于其第一个成员的GetHashCode函数值,那么修改了第一个成员的值,也就间接的修改了对象的GetHashCode值,从而对于一致性来说也是不满足的。

 

对于一二两条都不满足,去谈第三条是没有意义的,不过就函数本身来说,效率也和引用类型基本一样,没有采用特殊的算法,所以想得到比较好的效率也是不可能的。

 

经过逐个分析,再来重复一下我前面所说的。对于引用类型的GetHashCode函数来说,基本上是正确的,但是效率不高;而对于值类型而言,基本上是不正确的,而且效率也是不理想的。

 

那有人就说了,程序会要用到HashTable去存,必然会用到类型的GetHashCode函数,如何避免如上的错误,或者说提供一个比较正确的GetHashCode函数呢。那么接下来就分别说说如何去实现(这里所说的实现主要满足前两条即可,最后一条牵扯到Hash函数算法,这里不做讨论)。

 

这次先说说值类型,因为值类型本身提供的基本不正确。如果不想做过多处理,毕竟提供一个好的哈希函数不容易,那么从值类型的GetHashCode规律出发,即从类型自身元素出发。对于一个值类型,如果其本身存在某个数据可以唯一标明此类型对象,有点儿像数据库中的Key字段,那么用它作为类型的第一个元素。例如就前面所说的ErrorMessage来说,dtInvoked成员可以唯一表示这个类型数据,那么就可以如下修改。

        public struct ErrorMessage

        {

            private DateTime dtInvoked;

            private string strMsg;

            private int nErrorCode;

 

        }

 

这样就满足了验证第一条,对于第二条,就是要保证这个类型的对象通过GetHashCode能自始至终一样,就要防止第一个成员被修改,比较好的做法就是给它加上readonly标示,那么比较完整的样式应该如下。

        public struct ErrorMessage

        {

            private readonly DateTime dtInvoked;

            private string strMsg;

            private int nErrorCode;

 

        }

 

这样对于ErrorMessage类型的GetHashCode至少是正确的。有人说了,如果定义的类型没有一个单独成员能作为唯一标示,那我就建议你不要把这种类型的数据来产生Key。

 

接下来说说对于引用类型的GetHashCode函数改写。对于第一条来说,在引用类型中有可能重新编写Equals函数,那么类型自带的GetHashCode函数将不能适应这个要求,需要进行重写来适应这种改变。如何简便的改写GetHashCode函数而达到效果呢,这里可以延用前面值类型的做法,即选择一个能唯一标示这个对象的成员来生成HashCode,同时要避免这个成员被修改。

 

例如一个比较合理的引用类型的GetHashCode函数大致如下(此例引用于原书):

    public class Customer

    {

        private readonly string _name;

        private decimal _revenue;

        public Customer( string name ): this( name, 0 )

        {

        }

 

        public Customer( string name, decimal revenue )

        {

            _name = name;

            _revenue = revenue;

        }

 

        /// <summary>

        /// Name property which only can be accessed in reading mode

        /// </summary>

        public string Name

        {

            get{ return _name;}

        }

 

        /// <summary>

        /// Create a new object with new name

        /// </summary>

        /// <param name="newName"></param>

        /// <returns></returns>

        public Customer ChangeName( string newName )

        {

            return new Customer( newName, _revenue );

        }

 

        /// <summary>

        /// Customer hash code generated by name

        /// </summary>

        /// <returns></returns>

        public override int GetHashCode()

        {

            return _name.GetHashCode ();

        }

    }

 

对于Customer类型对象来说,它的HashCode是由其_name成员所决定的,所以不能轻易改变,如果通过调用ChangeName方法来替换原先的对象的时候,要首先操作HashTable,先把原先的删除,创建新的之后再保存。具体如下:

    Customer c1 = new Customer( "test1" );

    object orders = new object();

    myHashTable.Add( c1, orders );

 

    //Change name

    Customer c2 = c1.ChangeName( "test2" );

    object o = myHashTable[ c1 ];

    myHashTable.Remove( c1 );

    myHashTable.Add( c2, o );

 

对于如上中Custemer对象来说,只是为了产生在HashTable中所存对象的HashCode,当然在实际应用中,两者需要关联,否则使用HashTable存这些数据就没有任何意义了。

 

这样对于值类型和引用类型的GetHashCode改写到此基本已经结束了,显然如上的改写,只是为了保证类型的GetHashCode正确,但是对于其的效率并没有得到长足的进步,或者换句话来说,改写后的GetHashCode函数仍然保留HashTable使用效率不高。如何在GetHashCode函数使用比较好的哈希函数,使产生的HashCode具有比较好的分布,我在此不对它进行讨论,因为光这个问题就足够写好几本书的。

 

对于GetHashCode函数,大致就说到这儿,最后为了加深记忆,总结一下。

 

首先,在不重写此函数的情况下,这里主要说说使用当中应该注意的。

1.  不建议使用值类型对象的GetHashCode函数返回值来作为HashTable对象的Key;

2.  引用类型是可以使用的,但是要注意如果重写了Equals函数,一定要重写GetHashCode函数来达到一致;

 

再说说重写此函数时需要注意的。

1.  不管是值类型还是引用类型,要保证产生HashCode的成员不能被修改;

2.  对于产生HashCode的成员修改,要以产生新对象进行处理,同时要在使用端作相应的修改,即先删除旧的在添加新的。

 

使用类型的GetHashCode有微妙的危险,所以使用的时候要特别注意。

 
从STM32转战小华HC32F448:手把手教你搞定串口复用与超时中断(附完整代码) 本文详细介绍了从STM32迁移到小华HC32F448的串口开发实战指南,重点解析了HC32F448的GPIO复用系统和超时中断机制。通过对比STM32的架构差异,提供了完整的串口配置代码和优化方案,帮助开发者快速掌握HC32F448的串口通信技术,适用于工业控制等应用场景。 阅读详情

相关推荐

2025年最新Cesium学习示例:Cesium-Examples

2025年最新Cesium学习示例:Cesium-Examples

2403_88103571的博客 1011

More Effective C#——详析GetHashCode方法

GetHashCode函数一般是在操作HashTable或者Dictionary之类的数据集的时候被调用,目的是产生一个Key,为了方便在 HashTable或者 Dictionary中的检索。 每个类型,不管是值类型还是引用类型,都提供这个基本函数,同样也可以像重写ToString或者Equals函数一样去重写它。但是不建议重写此函数,而且在使用这个函数也需要加倍小心。...

aj 1090

DRAM 埋入式字线(Buried Wordline)技术深度解析:从二维平面到三维凹槽

埋入式字线(bWL)及其核心的埋入式沟道阵列晶体管(BCAT)架构,无疑是 DRAM 发展史上的一次关键性革命。它在约 40nm 技术节点被行业主流采纳,成功地帮助 DRAM 产业克服了传统平面晶体管的物理缩放极限。力挽狂澜的续命技术:bWL 通过引入三维沟道结构,从根本上解决了由短沟道效应和漏电流失控引发的数据保持危机,为经典的 1T1C(单晶体管 - 单电容)DRAM 单元架构延续了超过十年的生命力。协同优化的基石技术。

m0_37606066的博客 3333

C#GetHashCode()方法的使用

GetHashCode()方法 问题描述 往往我们会看到Equals()与GetHashCode()方法体一起使用,但对于为什么要使用却是迷迷糊糊,很多时候都是套用别人的,现在我就具体写写我的一些看法,如有写得不好的地方,请给予指正! 例如: public class DComparer : IEqualityComparer<double> { public bool Equals(double b1, double b2) { return (Math.Abs(b1 - b2) <

weixin_44899642的博客 6377

UE5多人联机实战:从零搭建LAN游戏房间(含UMG界面配置)

本文详细介绍了如何在UE5中从零开始搭建一个完整的局域网(LAN)多人游戏房间系统。内容涵盖项目基础配置、UMG主菜单设计、会话创建与搜索的核心实现,并重点解析了创建会话的关键参数配置与常见陷阱,帮助开发者快速掌握UE5多人联机开发的核心流程。

weixin_29001683的博客 690

手把手教你搭建硬件在回路(HIL)仿真系统:从Simulink建模到实时测试

本文详细介绍了如何从零开始搭建硬件在回路(HIL)仿真系统,涵盖Simulink建模、实时仿真、代码生成及调试等关键步骤。通过具体配置示例和优化技巧,帮助开发者解决复杂控制系统验证中的技术难题,提升半实物仿真的效率和可靠性。

weixin_30952535的博客 445

C# 面试问题高级:024 - GetHashCode方法的目的是什么?

哈希码是一个整数值,表示对象的唯一标识符。尽管两个不同的对象可以具有相同的哈希码(这种情况称为“哈希冲突”),但在理想情况下,不同对象应具有不同的哈希码。哈希码主要用于提高哈希表操作(如查找、插入和删除)的效率。当自定义类型需要作为字典或集合的键时,必须重写方法,以确保哈希码的正确性和一致性。否则,默认的实现可能会导致不可预测的行为。

caifox的博客 757

把 OData V4 服务真正发布出去:事务码 /IWFND/V4_ADMIN 在 SAP S/4HANA On-Premise 里的定位与用法

本文介绍了SAP S/4HANA中的事务码/IWFND/V4_ADMIN,这是SAP Gateway管理OData V4服务的关键工具。该事务码主要负责将后端注册的OData V4服务以服务组形式发布到前端,并维护系统别名路由、测试入口和日志跳转。与OData V2不同,V4采用服务组概念以支持跨服务导航,类似商场统一管理多个专柜。文章还通过实际案例说明,未发布服务组会导致BAS等工具无法发现OData V4服务,并提供了验证服务组状态的访问路径和ABAP示例代码。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 208

[C#] 每个 C# 开发人员都应该知道的事情#1:hash codes (哈希码)

译者总结:HashCode是为了给这个对象一个独一无二的id。让对象在哈希表内均匀的分布,使查找时,速度更快作为一名 C# 开发人员,显然需要掌握很多技能才能发挥作用:语言语法、框架类、第三方库、数据库、正则表达式、HTTP 协议等。

unity尧明的博客 1126

超临界二氧化碳发电技术深度解析

超临界二氧化碳发电技术正在突破传统热电转换的物理边界。作为新一代发

硬核科技 602

【异常】Cursor 模型服务地域访问限制报错的完整排查与解决方案This model provider doesn‘t serve your region

其本质是:Cursor客户端依赖的上游大语言模型服务提供商,基于自身服务的合规运营范围,对客户端发起的请求进行了来源地域权限校验;当系统检测到请求的网络来源地域,不在该模型服务商官方授权提供服务的地域范围内时,会触发服务访问拦截策略,拒绝响应本次模型调用请求,最终在客户端抛出该报错。合规声明:本文所提供的所有方案,均仅用于技术问题排查与解决,请您在使用过程中严格遵守当地法律法规,以及对应软件与模型服务提供商的用户协议与合规要求。更多关于AI+教育领域的干货,可以关注我的微信公众号一时间获取哦~

奶爸的编程之路,也就一周冷个三天 1134

C#的GetHashCode的用法

建议了解一下哈希和哈希表,就会知道这个哈希码(HashCode)是什么东西了。 简而言之,哈希代码是一个用于在相等测试过程中标识对象的数值。它还可以作为一个集合中的对象的索引。如果两个对象的 Equals 比较结果相等,则每个对象的 GetHashCode 方法都必须返回同一个值。 如果两个对象的比较结果不相等,这两个对象的 GetHashCode 方法不一定返回不同的值。 简而言之,如果你发现两个对象 GetHashCode() 的返回值相等,那么这两个对象就很可能是同一个对象;但如果返回值不相等,这两个

qq_18072961的博客 2230

实战:批量抓取免费代理IP全流程(附自动验证+失效剔除,爬虫抗封必备)

本文分享了一套从免费代理IP中筛选可用高匿代理的实用方案。作者通过半年实践,总结出"批量抓取+精准验证+动态维护"的方法,能从2000条免费IP中筛选出30-50条可用代理,满足中小规模爬虫需求。 方案包含四个核心环节: 多源代理抓取:针对快代理、西刺代理等不同网站定制解析规则 三层验证机制:检查有效性、匿名度和响应速度 代理池自动维护:实时淘汰失效IP Scrapy整合实战:提供可直接复用的代码 文章特别指出免费代理的三大痛点:存活率低(80%10分钟内失效)、匿名性差(仅5%是高匿代

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1602

[转载]《Effective C#》Item 10:小心GetHashCode函数所存在的陷阱

《Effective C#》Item 10:小心GetHashCode函数所存在的陷阱《Effective C#》Item 10:小心GetHashCode函数所存在的陷阱 GetHashCode函数,看了它的名字就...

congji3817的博客 131

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

转:《Effective C#》Item 10:小心GetHashCode函数所存在的陷阱

《Effective C#》Item 10:小心GetHashCode函数所存在的陷阱 GetHashCode函数,看了它的名字就知道它会被用在哪里。没错,这个函数一般是在操作HashTable或者Dictionary之类的数据集的时候被调用。每个类型,不管是值类型还是引用类型,都提供这个基本函数,同样也可以像重写ToString或者Equals函数一样去重写它。但是我这里要说

flyskylf的专栏 640

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

《Effective C#》Item 10:小心GetHashCode函数所存在的陷阱 (转)

GetHashCode函数,看了它的名字就知道它会被用在哪里。没错,这个函数一般是在操作HashTable或者Dictionary之类的数据集的时候被调用。每个类型,不管是值类型还是引用类型,都提供这个基本函数,同样也可以像重写ToString或者Equals函数一样去重写它。但是我这里要说的,不建议重写此函数,而且在使用这个函数也需要加倍小心。 Why? 有些人看了我所说的,会产生类似的...

weixin_33713503的博客 100

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

C#开发之——GetHashCode方法(6.4)

一 概述 C# GetHashCode方法返回当前System.Object的哈希代码,每个对象的哈希值都是固定的 该方法不含有任何参数,并且 不是静态方法,因此需要使用实例来调用该方法 由于该方法是在Object类中定义的,因此任何对象都可以直接调用该方法 <!--more--> 二 实例 创建两个Student类的对象,并分别计算其哈希值 2.1 代码 <span style="color:#333333">class Program {

我的博客 1094

C#中Equals和GetHashCode

Equals和GetHashCode Equals每个实现都必须遵循以下约定: 自反性(Reflexive): x.equals(x)必须返回true. 对称性(Symmetric): x.equals(y)为true时,y.equals(x)也为true. 传递性(Transitive): 对于任何非null的应用值x,y和z,如果x.equals(y)返回true,并且y.equ...

冯辉 1730

C#重写GetHashCode()

当你想要重写Equals()时,也应该同时重写GetHashCode()。若忘记此操作,编译器显示警告:在将类作为散列表集合(比如System.Collections.Hashtable和System.Collections.Generic,Dictionary)的键(key)使用时,最好也将GetHash()重写

先学再说 9849

c#:关于Object.Equals和Object.GetHashCode

参照: 优秀博文:《聊一聊C#的Equals()和GetHashCode()方法》 官方文档:《Object.GetHashCode Method》 看了微软官方的说明: 判断对象是否相等仅依赖于Equals方法; 如果判断两个对象相等,那么GetHasCode()返回值必然相等; 对象Equals判定不相等的对象,他们的GetHashCode()返回值仍然可以相等(但最好不相等); GetHashCode方法会在基于Hash算法存储数据的时候用到,存取数据时先用GetHashCode找到槽位,再用Equa

火焰 1101

【转载】聊一聊C#的Equals()和GetHashCode()方法

首先先谈一下Equals()这个方法: Equals()方法,来自于Object,是我们经常需要重写的方法。此方法的默认实现大概是这样的: public virtual bool Equals(object obj) {   if(obj==null) return false;   if(GetType() != obj.GetType()) return false; ...

weixin_34138521的博客 285

聊一聊C#的Equals()和GetHashCode()方法

聊一聊C#的Equals()和GetHashCode()方法 https://www.cnblogs.com/xiaochen-vip8/p/5506478.html 聊一聊C#的Equals()和GetHashCode()方法 博客创建一年多,还是第一次写博文,有什么不对的地方还请多多指教。 关于这次写的内容可以说是老生长谈,百度一搜一大堆。大神可自行绕路。 最近在看Jeffrey R...

kuangben2000的博客 431
上一篇: 使用Double-Buffer来实现无闪烁动态折线图
下一篇: 《Effective C#》Item 11:提倡使用foreach语句来进行循环操作
Knight94
博客等级 码龄25年 1013粉丝 87原创
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值