C# Object.GetHashCode

C# Object.GetHashCode,c#,.net,hash,gethashcode,C#,.net,Hash,Gethashcode,我的问题可能会重复,但我再次提问,因为我不明白这个问题的公认答案 首先,我有三个问题,如下所述: First type, 100 objects, 1000 iterations, 2072 msec First type, 1000 objects, 100 iterations, 2098 msec Second type, 100 objects, 1000 iterations, 1300 msec Second type, 1000 objects, 100 iterations, 1

我的问题可能会重复,但我再次提问,因为我不明白这个问题的公认答案

首先,我有三个问题,如下所述:

First type, 100 objects, 1000 iterations, 2072 msec
First type, 1000 objects, 100 iterations, 2098 msec
Second type, 100 objects, 1000 iterations, 1300 msec
Second type, 1000 objects, 100 iterations, 1319 msec
Third type, 100 objects, 100 iterations, 1487 msec
Third type, 1000 objects, 10 iterations, 13754 msec
但是,由于此索引可以在垃圾回收期间回收对象后重新使用,因此可以为两个不同的对象获取相同的哈希代码

这是真的吗?在我看来,两个对象不会有相同的哈希代码,因为在对象被垃圾收集(即不再存在)之前,对象的代码不会被重用

此外,两个表示相同值的对象只有在它们是完全相同的对象时才具有相同的哈希代码

这是个问题吗?例如,我想将一些数据与DOM树中的每个节点实例相关联。要做到这一点,“节点”必须具有标识或哈希代码,以便我可以将它们用作数据字典中的键。我想要的不是一个哈希代码,它能识别它是否是“完全相同的对象”,即“引用相等而不是“值相等”吗

“这种实现对于散列不是特别有用;因此,派生类应重写GetHashCode“

这是真的吗?如果它不适合散列,那么如果它有什么好处呢?为什么它甚至被定义为对象的方法


我的最后一个问题(对我来说可能是最重要的)是,如果我必须为具有“引用相等”语义的任意类型发明/重写GetHashCode()实现,那么以下是合理且良好的实现:

class SomeType
{
  //create a new value for each instance
  static int s_allocated = 0;
  //value associated with this instance
  int m_allocated;
  //more instance data
  ... plus other data members ...
  //constructor
  SomeType()
  {
    allocated = ++s_allocated;
  }
  //override GetHashCode
  public override int GetHashCode()
  {
    return m_allocated;
  }
}

编辑

仅供参考,我使用以下代码对其进行了测试:

    class TestGetHash
    {
        //default implementation
        class First
        {
            int m_x;
        }
        //my implementation
        class Second
        {
            static int s_allocated = 0;
            int m_allocated;
            int m_x;
            public Second()
            {
                m_allocated = ++s_allocated;
            }
            public override int GetHashCode()
            {
                return m_allocated;
            }
        }
        //stupid worst-case implementation
        class Third
        {
            int m_x;
            public override int GetHashCode()
            {
                return 0;
            }
        }

        internal static void test()
        {
            testT<First>(100, 1000);
            testT<First>(1000, 100);
            testT<Second>(100, 1000);
            testT<Second>(1000, 100);
            testT<Third>(100, 100);
            testT<Third>(1000, 10);
        }

        static void testT<T>(int objects, int iterations)
            where T : new()
        {
            System.Diagnostics.Stopwatch stopWatch =
                System.Diagnostics.Stopwatch.StartNew();
            for (int i = 0; i < iterations; ++i)
            {
                Dictionary<T, object> dictionary = new Dictionary<T, object>();
                for (int j = 0; j < objects; ++j)
                {
                    T t = new T();
                    dictionary.Add(t, null);
                }
                for (int k = 0; k < 100; ++k)
                {
                    foreach (T t in dictionary.Keys)
                    {
                        object o = dictionary[t];
                    }
                }
            }
            stopWatch.Stop();
            string stopwatchMessage = string.Format(
                "Stopwatch: {0} type, {1} objects, {2} iterations, {3} msec",
                typeof(T).Name, objects, iterations,
                stopWatch.ElapsedMilliseconds);
            System.Console.WriteLine(stopwatchMessage);
        }
    }
我的实现占用了默认实现的一半时间(但我的类型比我分配的m_数据成员的大小大)

我的实现和默认实现都是线性扩展的


相比之下,作为一种健全性检查,愚蠢的实现开始时很糟糕,扩展性也更差。

对于只需要引用相等的类,实际上不需要修改任何内容


另外,从形式上讲,这不是一个好的实现,因为它的分布很差。散列函数应该有一个合理的分布,因为它改进了散列桶分布,并间接地提高了使用散列表的集合的性能。正如我所说的,这是一个正式的答案,也是设计散列函数的指导原则之一。

哈希代码实现必须具有的最重要属性是:

如果两个对象比较相等,则它们必须具有相同的哈希代码。

如果您有一个类,其中该类的实例按引用相等进行比较,则不需要重写GetHashCode;默认实现保证相同引用的两个对象具有相同的哈希代码。(您在同一对象上调用同一方法两次,因此结果当然是相同的。)

如果您编写了一个类,该类实现了它自己的等式,而该等式不同于引用等式,那么您需要重写GetHashCode,以便比较为相等的两个对象具有相等的哈希代码

现在,你只需每次返回零就可以了。这将是一个糟糕的散列函数,但它是合法的

好的散列函数的其他属性包括:

  • GetHashCode不应引发异常

  • 可变对象在其可变状态上进行相等性比较,并因此在其可变状态上进行哈希,这很容易导致错误。您可以将对象放入哈希表中,对其进行变异,然后无法再次将其取出。请尝试永远不要在可变状态上进行相等性哈希或比较


  • GetHashCode应该非常快——记住,一个好的哈希算法的目的是提高查找的性能。如果哈希很慢,那么查找就不能很快

  • 不作为相等进行比较的对象应具有不同的哈希代码,在32位整数的整个范围内均匀分布

问题:

这是真的吗?在我看来,两个对象不会有相同的哈希代码,因为 在对象被垃圾收集(即不再存在)之前,对象的代码不会被重用

如果默认情况下GetHashCode实现生成相同的哈希代码,则两个对象可能共享相同的哈希代码,因为:

  • 默认GetHashCode结果在对象的生存期内不应更改,默认实现确保了这一点。如果可以更改,Hashtable等类型将无法处理此实现。这是因为默认哈希代码应该是唯一实例标识符的哈希代码(即使没有这样的标识符:))
  • GetHashCode值的范围是整数(2^32)的范围。
  • 结论: 将2^32个强引用对象分配给(在Win64上必须很容易)即可达到限制


    最后,中有一条明确的语句:GetHashCode方法的默认实现不保证不同对象的唯一返回值。此外,.NET Framework不保证GetHashCode方法的默认实现,并且它返回的值在不同版本的.NET之间是相同的框架。因此,此方法的默认实现不得用作哈希目的的唯一对象标识符。

    为了线程安全,可能使用volatile声明和互锁递增器。如果由于线程不安全而偶尔发生意外冲突,这会有任何影响吗?我认为GetHashCode的实现不需要保证不同对象的唯一返回值,相反,如果它们基本上是唯一的,就足够了。非常有趣的是,您的简单实现显著提高了查找性能,但代价是4B/实例。一个