是否在对象的生存期内保证GetHashCode相同?

时间:2019-05-02 20:58:13

标签: c# .net-core

我讨厌击败dead horse。在@ eric-lippert的blog中,他说:

  

对象的哈希值在整个生命周期中都是相同的

然后跟进:

  

但是,这只是一个理想的指导原则

所以,我的问题是这个。

对于POCO(什么也不能覆盖)或框架(例如FileInfoProcess)对象,是否保证GetHashCode()方法的返回值在其生存期内是相同的?

P.S。我说的是预先分配的对象。 var foo = new Bar();foo.GetHashCode()始终返回相同的值。

2 个答案:

答案 0 :(得分:4)

如果您查看MSDN documentation,将会发现有关 GetHashCode 方法的默认行为的以下说明:

  

如果未覆盖GetHashCode,则引用类型的哈希码为   通过调用基类的Object.GetHashCode方法进行计算,   它基于对象的引用来计算哈希码;更多   有关信息,请参见RuntimeHelpers.GetHashCode。换句话说,两个   ReferenceEquals方法返回true的对象具有   相同的哈希码。如果值类型未覆盖GetHashCode,则   基类的ValueType.GetHashCode方法使用反射来   根据类型字段的值计算哈希码。在   换句话说,其字段具有相等值的值类型具有相等的值   哈希码

根据我的理解,我们可以假设:

  • 对于引用类型(不会覆盖Object.GetHashCode) 给定实例的哈希码的值保证为 在实例的整个生命周期中都是相同的(因为内存 对象存储的地址在其期间不会更改 一生)
  • 对于值类型(不会覆盖Object.GetHashCode),它取决于:如果值类型是不可变的,则哈希码不会 在其生命周期内发生变化。如果,否则,其字段的值 可以在创建后更改,然后其哈希码也将更改。 请注意,值类型通常是不可变的。

重要编辑

正如上面的评论中指出的那样,.NET垃圾收集器可以决定在对象生存期内移动对象在内存中的物理位置,换句话说,可以将对象“重定位”到托管内存中。

这很有意义,因为垃圾回收器负责管理创建对象时分配的内存。

经过一些搜索并根据this stackoverflow question(阅读用户@supercat提供的注释),此重定位似乎不会在对象实例的生存期内更改其哈希码 ,因为哈希码只计算一次(第一次请求其值),然后将计算的值保存起来,以后再使用(当 再次请求哈希码值。)

总而言之,根据我的理解,您只能假设给定两个引用,它们指向内存中的同一对象,它们的哈希码将始终相同。换句话说,如果Object.ReferenceEquals(a,b),则a.GetHashCode()== b.GetHashCode()。此外,似乎给定对象实例的哈希码在整个生命周期中都将保持不变,即使对象的物理内存地址被垃圾收集器更改了。

使用哈希码的注意事项

始终记住重要的一点是,哈希码是在.NET框架中引入的,其唯一目的是处理哈希表数据结构。

为了确定要用于给定的存储桶,采用相应的 key 并计算其哈希码(准确地说,存储桶索引是通过对GetHashCode调用返回的值应用一些规范化来获得的,但细节对于此讨论并不重要)。换句话说,在.NET哈希表实现中使用的哈希函数基于密钥的哈希码的计算。

这意味着哈希码的唯一安全用法是平衡哈希表,正如Eric Lippert here所指出的那样,因此请勿编写依赖于哈希码值的代码用于任何其他目的。 / p>

答案 1 :(得分:2)

有三种情况。

  1. 不覆盖GetHashCode
  2. 的类
  3. 不覆盖GetHashCode的结构
  4. 确实覆盖GetHashCode
  5. 的类或结构

如果一个类没有覆盖GetHashCode,则使用辅助函数RuntimeHelpers.GetHashCode的返回值。每次为同一对象调用时,它将返回相同的值,因此,一个对象将始终具有相同的哈希码。请注意,此哈希代码特定于单个AppDomain-重新启动应用程序或创建另一个AppDomain,可能会导致您的对象获得不同的哈希代码。

如果结构未覆盖GetHashCode,则根据其成员之一的哈希码生成哈希码。当然,如果您的结构是可变的,则该成员可以随时间变化,因此哈希码也可以随时间变化。即使该结构是不可变的,该成员本身也可能会发生突变,并可能返回不同的哈希码。

如果某个类或结构确实覆盖了GetHashCode,则所有投注均关闭。有人可以通过返回一个随机数来实现GetHashCode-这是一件很愚蠢的事情,但这是完全可能的。该对象很有可能是可变的,其哈希码可能基于其成员,这两个成员都可以随时间变化。

为可变对象或以哈希代码可以随时间变化的方式(在给定的AppDomain中)实现GetHashCode通常是一个坏主意。在这种情况下,诸如Dictionary<TKey, TValue>之类的类所做的许多假设都将分解,您可能会看到奇怪的行为。