【问题标题】:Implementation of Object.GetHashCode()Object.GetHashCode() 的实现
【发布时间】:2014-11-28 20:53:22
【问题描述】:

我正在阅读Effective C#,有一条关于Object.GetHashCode()的评论我不明白:

Object.GetHashCode() 使用System.Object 类中的内部字段来生成哈希值。创建的每个对象都被分配一个唯一的对象键,在创建时存储为整数。
这些键从 1 开始,每次获得任何类型的新对象时递增 创建的。对象标识字段在System.Object 构造函数中设置,以后无法修改。 Object.GetHashCode() 将此值作为给定对象的哈希码返回。

我试图查看Object.GetHashCode() 的文档,但没有找到任何相关信息。

我写了一段简单的代码来打印新生成对象的哈希码:

using System;

namespace TestGetHashCode
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 100; i++)
            {
                object o = new object();
                Console.WriteLine(o.GetHashCode());
            }
        }
    }
}

打印的前几个数字是:

37121646,
45592480,
57352375,
2637164,
41014879,
3888474,
25209742,
26966483,
31884011

这似乎不适合

这些键从 1 开始,并在每次创建任何类型的新对象时递增...Object.GetHashCode() 返回此值

然后,为了在System.Object 中找到这个“内部字段”,我尝试使用ReSharper decompiled sources,但我找到的代码是

[TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries")]
[__DynamicallyInvokable]
public virtual int GetHashCode()
{
  return RuntimeHelpers.GetHashCode(this);
}

再次使用反编译的源代码,我发现RuntimeHelpers.GetHashCode 被实现为

[SecuritySafeCritical]
[__DynamicallyInvokable]
[MethodImpl(MethodImplOptions.InternalCall)]
public static int GetHashCode(object o);

the MethodImpl attribute 之后,我似乎无法查看实现,这对我来说是一条死胡同。

有人可以解释作者的评论(第一句话)吗?

Object 类中的内部字段是什么以及它如何用于实现Object.GetHashCode()

【问题讨论】:

  • @Yuval Itzchakov:讽刺吗?否则,你为什么这么认为?
  • @Yuval Itzchakov:为什么你认为GetHashCode() 必须返回一个唯一标识符?它被称为Get*Hash*Code() 而不是Get*UniqueIdentifier*() 有目的
  • @Yuval Itzchakov:“将为所有返回的对象生成相等性”——这是不正确的。如果它们的哈希码匹配,则永远不应该平等对待对象。实际上恰恰相反:相等的对象必须返回相等的哈希码,但相等的哈希码并不意味着对象相等。 msdn.microsoft.com/en-us/library/… "你不应该假设相等的哈希码意味着对象相等。"
  • @Yuval Itzchakov:“谁只看哈希码相等”——没有人应该这样做。如果有人做了一些愚蠢的事情——这不是鼓励他们继续这样做的理由。 .net 并非如此。如果是第 3 方库的情况,则必须将其报告为错误并进行修复。

标签: c# object gethashcode


【解决方案1】:

好的,我最好把这个写下来。这本书非常不准确。 Object.GetHashCode() 的值在 CLR 内部生成,并在第一次调用 GetHashCode() 时按需计算。我将引用 SSCLI20 发行版中的代码,clr/src/vm/thread.h 具有生成数字的函数,它看起来像这样(为便于阅读而编辑):

inline DWORD GetNewHashCode()
{
    // Every thread has its own generator for hash codes so that we won't get into a 
    // situation where two threads consistently give out the same hash codes.
    // Choice of multiplier guarantees period of 2**32
    // see Knuth Vol 2 p16 (3.2.1.2 Theorem A).
    DWORD multiplier = m_ThreadId*4 + 5;
    m_dwHashCodeSeed = m_dwHashCodeSeed*multiplier + 1;
    return m_dwHashCodeSeed;
}

之后它被存储在对象的所谓同步块中,以便后续调用返回相同的值。生成的 32 位中只有 26 位实际存储,同步块需要空间用于一些状态位。仍然足以生成非常高质量的哈希码,冲突非常罕见。

该代码中 m_ThreadId 变量的存在可以使用一个解释。为每个单独的线程存储随机数生成器种子。避免锁定的技巧。

m_dwHashCodeSeed 在 Thread 构造函数中初始化如下:

   // Initialize this variable to a very different start value for each thread
   // Using linear congruential generator from Knuth Vol. 2, p. 102, line 24
   dwHashCodeSeed = dwHashCodeSeed * 1566083941 + 1;
   m_dwHashCodeSeed = dwHashCodeSeed;

与:

   static  DWORD dwHashCodeSeed = 123456789;

【讨论】:

  • 感谢您的回答。从这段代码看来,GetNewHashCode 是一个仅依赖于 m_ThreadId 的函数,因此对于同一个线程,它在每次调用时都会生成相同的哈希码。我在这里错过了什么?
  • 不,它取决于 m_dwHashCodeSeed,每次生成新的哈希码时更新。每个线程都以不同的种子开始,底部 sn-p。 m_ThreadId 只是增加了额外的随机性,Donald Knuth 对此有所帮助。不要过分关注与此相关的线程,这只是一个无锁代码技巧。锁很贵。两个线程同时调用 GetHashCode 存在一些技巧,我将其省略以使其易于理解。
  • 我了解它的工作原理,并感谢您的写作,但究竟为什么它会这样工作?不需要像他们正在做的那样“希望”没有冲突,对吧?为什么不根据对象的唯一标识符为对象生成哈希?或者,如果不存在,则使用每次递增 1 的 UID 并将其存储在 SYNC 块中?与上述解决方案相同,只是可能的绝对最小冲突率(仅在 UID 溢出时发生冲突)
猜你喜欢
  • 2023-03-10
  • 2011-07-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-08
  • 1970-01-01
相关资源
最近更新 更多