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

C# 实现文件和字符串的AES加密解密 C# 实现文件和字符串的AES128加密解密 阅读详情

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有微妙的危险,所以使用的时候要特别注意。

 
VC Spyglass实战指南:从零开始掌握CDC约束配置与调试 本文详细介绍了如何使用VC Spyglass进行CDC约束配置与调试,从基础环境搭建到典型CDC问题调试实战,帮助开发者快速掌握跨时钟域验证技术。通过TCL脚本和SDC约束的深度解析,提升CDC验证效率,避免常见设计风险。 阅读详情

相关推荐

android+音乐节拍检测,科学网—音乐节拍跟踪或音乐节拍检测软件,LilyBeats alpha - 石自强的博文...

我们设计并实现了一种用于实时跟踪音乐节拍的软件。该软件可以用于智能灯方案提供商,为其提供灯光随音乐变化的核心解决方案。首先给出软件的下载链接:或者音乐节拍是乐曲中表示固定单位时值和强弱规律的组织形式,亦称拍子。它有两个特性:周期性和连续性。节拍周期性表现为节拍结构,是乐曲中周期性出现的节奏序列。节拍连续性表现为音乐的平均速度,其单位为bpm(beat per minute每分钟音乐节拍的个数)。音...

weixin_39571403的博客 1401

More Effective C#——详析GetHashCode方法

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

aj 1090

告别报错!Win10下Autodock Vina 1.2.3保姆级安装与避坑指南

本文提供了Win10系统下Autodock Vina 1.2.3的完整安装与实战指南,涵盖环境配置、分子文件预处理、对接参数优化及结果分析等关键步骤。通过详细的避坑技巧和优化方案,帮助用户高效完成分子对接任务,特别适合生物信息学研究人员和药物发现领域的初学者。

weixin_29969209的博客 405

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

基于R语言的DICE模型实践技术应用;预测不同减排路径下的经济增长、碳排放量、气候变化程度以及社会福利变化

DICE模型是一个动态综合气候经济模型,由诺贝尔经济学奖得主William Nordhaus开发,用于评估气候变化对经济的影响以及不同减排政策的经济成本和效益。以下是一个关于DICE模型在气候变化影响评估中的实际应用案例,结合R语言代码进行讲解。

KY_chenzhao的博客 869

STM32 ST-LINK Utility 无法烧录程序的解决方法

当你打开STM32 ST-LINK Utility,硬件连接好st-link V2,打开了要烧写的文件,点那个Program verify烧录时, 如果出错了: Programming error @ 0x08000000! 我的方法是: 点你想要烧录那个HEX文件的小格,复制 然后去到你前面的文件的格子里,点粘贴: 成功! ...

wocute的博客 9521

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

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

caifox的博客 757

深入解析luckyExcel处理xlsx与xls文件的实战技巧

本文深入解析了使用luckyExcel库在前端处理.xlsx与.xls文件的实战技巧。针对.xlsx文件,可直接调用API转换;对于老旧的.xls格式,则需结合SheetJS进行预处理。文章详细介绍了核心API的使用、两种格式的处理流程、数据提取方法以及性能优化建议,为开发在线Excel编辑应用提供了完整解决方案。

cherry的博客 818

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

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

unity尧明的博客 1126

openEuler Embedded新增软件包及在线安装软件方法

openEuler Embedded是基于Yocto构建的嵌入式操作系统,默认不包含dnf/apt软件包管理器,主要使用rpm格式。新增软件需通过manifest文件检查是否集成,未集成则需适配新配方。对于在线安装需求,可采用轻量级容器方案:通过isulad运行openEuler服务器版容器镜像,在容器内使用dnf命令安装软件(如Nginx),实现服务器生态兼容。部署前提包括设备支持、存储空间(≥10GB)和网络连接,需先安装并启动isulad服务。这种方法结合了嵌入式系统的轻量化特性和服务器系统的软件管理

专注嵌入式开发与 Linux 系统应用实战,分享芯片驱动、设备调试、系统工具开发干货。 1225

使用Jacob实现将Word文件转换为Pdf文件

如果是首次使用 jacob,建议先直接从 github上下载 zip包,因为在实现Word转Pdf的时候,不仅需要用到 jacob.jar,还需要 jacob.dll 库,而zip包中都有。官方使用文档可以参考:https://github.com/freemansoft/jacob-project/blob/main/docs/UsingJacob.md。// 如果安装的是微软的 Office,需要使用 Word.Application。

qq_41672950的博客 750

C#的GetHashCode的用法

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

qq_18072961的博客 2230

Python面试题及详细答案150道(126-135) -- 数据库交互篇

本文介绍了Python中操作MySQL、SQLite数据库及ORM框架的核心技术。主要内容包括:1. 使用pymysql连接MySQL数据库,演示了连接配置、SQL执行和事务管理;2. ORM原理及主流框架SQLAlchemy和Django ORM的示例;3. 通过sqlite3模块操作轻量级SQLite数据库;4. 数据库事务的ACID特性解析。这些内容涵盖了Python数据库开发的关键知识点,适合准备前后端面试的开发者复习核心概念和实际应用。

大剑师兰特的GIS世界 1729

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

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

congji3817的博客 131

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

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

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

flyskylf的专栏 640

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

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

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

weixin_33713503的博客 100

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

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、付费专栏及课程。

余额充值