当前位置:  开发笔记 > 编程语言 > 正文

重写System.Object.GetHashCode的最佳算法是什么?

如何解决《重写System.Object.GetHashCode的最佳算法是什么?》经验,为你挑选了13个好方法。

在.NET GetHashCode方法中,很多地方都使用.NET 方法.特别是在快速查找集合中的项目或确定相等性时.是否有关于如何GetHashCode为我的自定义类实现覆盖的标准算法/最佳实践,因此我不会降低性能?



1> Jon Skeet..:

我通常会使用Josh Bloch 精彩的 Effective Java中的实现.它很快并且创建了一个非常好的哈希,不太可能导致冲突.选择两个不同的素数,例如17和23,并执行:

public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = 17;
        // Suitable nullity checks etc, of course :)
        hash = hash * 23 + field1.GetHashCode();
        hash = hash * 23 + field2.GetHashCode();
        hash = hash * 23 + field3.GetHashCode();
        return hash;
    }
}

正如评论中所指出的,你可能会发现最好选择一个大素数乘以.显然486187739是好的...虽然我看到的大多数例子都是小数字倾向于使用素数,但至少有类似的算法,其中经常使用非素数.例如,在后来的非FNV例子中,我使用的数字显然效果很好 - 但初始值不是素数.(乘法常数虽然素数.我不知道它有多重要.)

这比XOR出于哈希码的常见做法更好,主要有两个原因.假设我们有一个包含两个int字段的类型:

XorHash(x, x) == XorHash(y, y) == 0 for all x, y
XorHash(x, y) == XorHash(y, x) for all x, y

顺便说一下,早期的算法是C#编译器当前用于匿名类型的算法.

这个页面提供了很多选择.我认为在大多数情况下,上述情况"足够好"并且非常容易记住并且正确.所述FNV替代方案是同样简单,但使用不同的常数和XOR代替ADD作为组合操作.它看起来的东西像下面的代码,但正常的FNV算法对每个字节进行操作,所以这将需要修改来执行的,而不是每32位的哈希值每字节一个迭代.FNV也是为可变长度的数据而设计的,而我们在这里使用它的方式总是针对相同数量的字段值.对这个答案的评论表明,这里的代码实际上并不像上面的添加方法那样(在测试的示例中).

// Note: Not quite FNV!
public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = (int) 2166136261;
        // Suitable nullity checks etc, of course :)
        hash = (hash * 16777619) ^ field1.GetHashCode();
        hash = (hash * 16777619) ^ field2.GetHashCode();
        hash = (hash * 16777619) ^ field3.GetHashCode();
        return hash;
    }
}

请注意,有一点需要注意的是,理想情况下,在将其添加到依赖于哈希代码的集合之后,应该防止对等式敏感(因此对哈希码敏感)状态的更改.

根据文件:

您可以为不可变引用类型重写GetHashCode.通常,对于可变引用类型,只有在以下情况下才应覆盖GetHashCode:

您可以从不可变的字段计算哈希码; 要么

您可以确保在对象包含在依赖于其哈希代码的集合中时,可变对象的哈希码不会更改.


23是不是一个好的选择,因为(截至.net 3.5 SP1)`Dictionary `假设分布模数为某些素数.23是其中之一.因此,如果您有一个容量为23的字典,则只有对"GetHashCode"的最后一个贡献会影响复合哈希码.所以我宁愿用29而不是23.
@CodeInChaos:只有最后一个贡献会影响存储桶 - 所以在最坏的情况下,它可能必须查看字典中的所有23*条目.它仍然会检查每个条目的实际哈希码,这将是便宜的.如果你有一本很小的字典,那就不太重要了.
@Vajda:我通常使用0作为`null`的有效哈希码 - 这与忽略该字段不同.
是的,Int64.GetHashCode正是如此.当然,在Java中需要拳击.这让我想起了 - 添加书籍链接的时间......
您提到的书中描述的算法实际上更加详细,它特别描述了对字段的不同数据类型要做什么.例如:对于long use(int)(field ^ f >>> 32)类型的字段,而不是简单地调用GetHashcode.是long.GetHashCodes实现那种方式?
@pomeroy:这取决于项目设置.基本上,您为程序集提供已选中或未选中的默认上下文.
@乔恩:我要问,尽管已经已经打开[我自己的问题(http://stackoverflow.com/q/4654227/482691)的话题,但什么是一个很好的VB版本,因为VB缺乏`检查`和`unchecked`关键字?我试图使tmpHash一个Int64和AND'ing的低8位(每[接受的答案(http://stackoverflow.com/questions/4654227/overriding-gethashcode-in-vb-without-checked-unchecked-keyword-支持/ 4656890#4656890)我的问题),但在一个足够大的足够的字段集,它在某种程度上造成了计算,换到0的循环的剩余部分.
@Jon:VB显然检查了很多东西.对于要求将无符号数字转换为带符号的数字,然后让你向左或向右移动它们,这是一种迷恋.这让我爬上墙壁,越过天花板.我正在尝试实现Jenkins哈希来解决缺少检查/未检查的问题(旋转哈希也可以解决问题,但我担心与输入的哈希冲突).使用单独的C#库是我想要避免的,因为它基本上承认失败.如果我达到这一点,我应该用C#重新编写整个项目.
@pomeroy:VB不像C#那样精细.因为它缺少上述两个关键字,所以您唯一的选择是删除整个项目的nteger溢出.我想如果您的项目完成并且通常经过良好测试,那么删除溢出检查是一件安全的事情.但是,在构建和调试时,这些检查很好,因为它们会突出显示要修复的错误.我打开Connect Ticket#636564与Microsoft建议在下一个.NET版本中包含已检查/未检查的关键字支持.但不确定他们是否会这样做.
仅供参考,Visual Studio 2017可以在没有ReSharper的情况下生成`GetHashCode()`。https://docs.microsoft.com/zh-cn/visualstudio/ide/reference/generate-equals-gethashcode-methods?view=vs-2017
是不是'未经检查'不必要的b/c CLR默认会溢出?
@FredOverflow:我不知道它背后的所有原因的确切细节,但从0开始意味着如果单个字段哈希为零,则哈希将保持为零...并且这可能并不罕见(例如整数值零可能会哈希到零).只是一个猜测,但我怀疑有一个常量与每个字段传播是有用的.这真的只是从Effective Java复制而来:)
@KChaloux:这完全取决于你想要的平等意味着什么.通常,包含可变数据是个坏主意.
你会如何处理无效?如果只是忽略那个字段,那么对于A = null,B ="ss",对于A ="ss",B = null,我们将得到colisions.将每个字段与不同的素数相乘是不是更好?
如果您从http://stackoverflow.com/a/2816747/21499获得了号码486187739 - 我实际上打算推荐92821.
@AhmadSiavosh:不,这是一个糟糕的想法,因为你想要不同但相同的对象拥有相同的哈希码.(我不认为object.GetHashCode也保证是独一无二的.它可能"非常不可能发生碰撞",但这不是一回事.)

2> Rick Love..:
匿名类型

Microsoft已经提供了一个很好的通用HashCode生成器:只需将属性/字段值复制到匿名类型并对其进行哈希:

new { PropA, PropB, PropC, PropD }.GetHashCode();

这适用于任意数量的属性.它不使用拳击.它只是使用框架中已经实现的匿名类型的算法.

ValueTuple - C#7的更新

正如@cactuaroid在评论中提到的,可以使用值元组.这节省了一些键击,更重要的是在堆栈上执行(没有垃圾):

(PropA, PropB, PropC, PropD).GetHashCode();

(注意:使用匿名类型的原始技术似乎在堆上创建了一个对象,即垃圾,因为匿名类型是作为类实现的,尽管这可能会被编译器优化.对这些选项进行基准测试会很有趣,但是元组选项应该是优越的.)


是的,匿名`GetHashCode`实现是非常有效的(BTW它与Jon Skeet的答案相同),但这个解决方案的唯一问题是你在任何`GetHashCode`调用中生成一个新实例.它可能有点开销,特别是在密集访问大散列集合的情况下......
可以说'新{PropA,PropB,PropC,PropD} .GetHashCode()`
VB.NET必须在匿名类型创建中使用Key:`New With {Key PropA} .GetHashCode()`否则GetHashCode将不会为具有相同'identify'属性的不同对象返回相同的哈希码.
@digEmAll好点,我没有考虑创建新对象的开销.Jon Skeet的答案是最有效的,不会使用拳击.(@Kumba要解决VB中未选中的问题,只需使用Int64(long)并在计算后截断它.)
@Keith在这种情况下,我会考虑将IEnumerable保存为某个列表值,而不是每次计算哈希码时枚举它.每次在GetHashCode中包含ToList可能会在许多情况下损害性能.
这适用于VB w/.NET 4.0,但查看IL,它使用`box`调用,因为该类型使用泛型.没有取消装箱,但从我在这里的阅读中,仅仅存在拳击表明这可能有点低效.看起来似乎是VB的唯一选择,因为没有相应的`checked` /`unchecked'.
对于这样的人,`(PropA,PropB,PropC,PropD).GetHashCode()现在可以在C#7上使用,而无需担心GC压力@digEmAll。[快速简单的哈希码组合](/sf/ask/17360801/#47480816)
在VB.Net中:`New With {PropA,PropB,PropC,PropD} .GetHashCode()`
不要忘记枚举你的IEnumerables或坏事将会发生.`new {PropA,PropB,C = PropC.ToList()}.GetHashCode()`
@Keith:哈希码不必受对象的*所有*属性影响。哈希码只需要给您的对象“足够好”的分配即可。并且应该*快速*进行计算。忽略枚举。而且,如果您有列表,请不要包括整个列表。使用`Count`,可能使用第一个元素(如果没有元素,则使用零)。除非您的班级除了清单上没有其他变化;否则,在这种情况下,最好按照Rick的建议缓存列表的哈希值。回想一下,根据定义,对象的哈希值必须始终相同。如果集合发生变化,请勿将其包括在哈希计算中。

3> nightcoder..:

这是我的哈希码助手.
它的优点是它使用泛型类型参数,因此不会导致装箱:

public static class HashHelper
{
    public static int GetHashCode(T1 arg1, T2 arg2)
    {
         unchecked
         {
             return 31 * arg1.GetHashCode() + arg2.GetHashCode();
         }
    }

    public static int GetHashCode(T1 arg1, T2 arg2, T3 arg3)
    {
        unchecked
        {
            int hash = arg1.GetHashCode();
            hash = 31 * hash + arg2.GetHashCode();
            return 31 * hash + arg3.GetHashCode();
        }
    }

    public static int GetHashCode(T1 arg1, T2 arg2, T3 arg3, 
        T4 arg4)
    {
        unchecked
        {
            int hash = arg1.GetHashCode();
            hash = 31 * hash + arg2.GetHashCode();
            hash = 31 * hash + arg3.GetHashCode();
            return 31 * hash + arg4.GetHashCode();
        }
    }

    public static int GetHashCode(T[] list)
    {
        unchecked
        {
            int hash = 0;
            foreach (var item in list)
            {
                hash = 31 * hash + item.GetHashCode();
            }
            return hash;
        }
    }

    public static int GetHashCode(IEnumerable list)
    {
        unchecked
        {
            int hash = 0;
            foreach (var item in list)
            {
                hash = 31 * hash + item.GetHashCode();
            }
            return hash;
        }
    }

    /// 
    /// Gets a hashcode for a collection for that the order of items 
    /// does not matter.
    /// So {1, 2, 3} and {3, 2, 1} will get same hash code.
    /// 
    public static int GetHashCodeForOrderNoMatterCollection(
        IEnumerable list)
    {
        unchecked
        {
            int hash = 0;
            int count = 0;
            foreach (var item in list)
            {
                hash += item.GetHashCode();
                count++;
            }
            return 31 * hash + count.GetHashCode();
        }
    }

    /// 
    /// Alternative way to get a hashcode is to use a fluent 
    /// interface like this:
/// return 0.CombineHashCode(field1).CombineHashCode(field2). /// CombineHashCode(field3); ///
public static int CombineHashCode(this int hashCode, T arg) { unchecked { return 31 * hashCode + arg.GetHashCode(); } }

它还有扩展方法来提供流畅的界面,所以你可以像这样使用它:

public override int GetHashCode()
{
    return HashHelper.GetHashCode(Manufacturer, PartN, Quantity);
}

或者像这样:

public override int GetHashCode()
{
    return 0.CombineHashCode(Manufacturer)
        .CombineHashCode(PartN)
        .CombineHashCode(Quantity);
}


顺便提一下,31是CPU上的移位和减法,这是非常快的.
@ChuiTey这是所有[Mersenne Primes](http://en.wikipedia.org/wiki/Mersenne_prime)的共同点.
不需要单独使用`T []`,因为它已经是`IEnumerable `
您可以重构这些方法并将核心逻辑限制为一个函数
@nightcoder你可以使用[params](https://msdn.microsoft.com/en-us/library/w5zay9db.aspx).

4> Wahid Shalal..:

我在Helper库中有一个Hashing类,我将它用于此目的.

///  
/// This is a simple hashing function from Robert Sedgwicks Hashing in C book.
/// Also, some simple optimizations to the algorithm in order to speed up
/// its hashing process have been added. from: www.partow.net
/// 
/// array of objects, parameters combination that you need
/// to get a unique hash code for them
/// Hash code
public static int RSHash(params object[] input)
{
    const int b = 378551;
    int a = 63689;
    int hash = 0;

    // If it overflows then just wrap around
    unchecked
    {
        for (int i = 0; i < input.Length; i++)
        {
            if (input[i] != null)
            {
                hash = hash * a + input[i].GetHashCode();
                a = a * b;
            }
        }
    }

    return hash;
}

然后,您只需将其用作:

public override int GetHashCode()
{
    return Hashing.RSHash(_field1, _field2, _field3);
}

我没有评估其表现,所以欢迎任何反馈.


好吧,如果字段是值类型,它将导致装箱.
"可以通过捕获OverflowException来增强"`unchecked`的重点是避免`GetHashCode`上需要的溢出异常.因此,如果值溢出`int`并且它根本没有伤害则不正确.

5> Şafak Gür..:

这是使用Jon Skeet实现的助手类.

public static class HashCode
{
    public const int Start = 17;

    public static int Hash(this int hash, T obj)
    {
        var h = EqualityComparer.Default.GetHashCode(obj);
        return unchecked((hash * 31) + h);
    }
}

用法:

public override int GetHashCode()
{
    return HashCode.Start
        .Hash(_field1)
        .Hash(_field2)
        .Hash(_field3);
}

如果要避免为System.Int32编写扩展方法:

public struct HashCode
{
    private readonly int _value;

    public HashCode(int value) => _value = value;

    public static HashCode Start { get; } = new HashCode(17);

    public static implicit operator int(HashCode hash) => hash._value;

    public HashCode Hash(T obj)
    {
        var h = EqualityComparer.Default.GetHashCode(obj);
        return unchecked(new HashCode((_value * 31) + h));
    }

    public override int GetHashCode() => _value;
}

它仍然是通用的,它仍然避免任何堆分配,它使用完全相同的方式:

public override int GetHashCode()
{
    // This time `HashCode.Start` is not an `Int32`, it's a `HashCode` instance.
    // And the result is implicitly converted to `Int32`.
    return HashCode.Start
        .Hash(_field1)
        .Hash(_field2)     
        .Hash(_field3);
}

马丁评论后更新:

obj != null 导致拳击所以我切换到默认比较器.

有关默认比较器的性能,请参阅此答案.

有关空值的哈希码的讨论,请参阅此问题.

编辑(2018年5月):

EqualityComparer.Defaultgetter现在是一个JIT内在 - 在这篇博客文章中,Stephen Toub提到了pull请求.



6> Bert Huijben..:

在大多数情况下,Equals()比较多个字段,如果你的GetHash()在一个字段或多个字段上进行哈希处理并不重要.您只需要确保计算哈希值非常便宜(请不要分配)和快速(没有繁重的计算,当然也没有数据库连接)并提供良好的分发.

繁重的工作应该是Equals()方法的一部分; 哈希应该是一个非常便宜的操作,以便尽可能少地调用Equals().

最后一个提示:不要依赖GetHashCode()在多个应用程序运行中保持稳定.许多.Net类型不保证其哈希码在重新启动后保持不变,因此您只应在内存数据结构中使用GetHashCode()的值.


"在大多数情况下,Equals()比较多个字段,如果你的GetHash()在一个字段或多个字段上进行哈希处理并不重要." 这是危险的建议,因为对于仅在非散列字段中不同的对象,您将获得散列冲突.如果经常发生这种情况,基于散列的集合(HashMap,HashSet等)的性能将降低(在最坏的情况下最多为O(n)).
这实际上发生在Java中:在早期版本的JDK String.hashCode()中只考虑字符串的开头; 如果您使用字符串作为HashMaps中的键,这会导致性能问题,而HashMaps在最后只会有所不同(这在URL中很常见).因此改变了算法(我认为在JDK 1.2或1.3中).
如果那个字段"提供了良好的分布"(我的答案的最后一部分),那么一个字段就足够了.如果它**不能提供良好的分布**,那么(并且就在那时)你需要另一个计算.(例如,只使用****提供良好分布的另一个字段,或使用多个字段)

7> Jon Hanna..:

直到最近,我的答案将非常接近Jon Skeet在这里.但是,我最近启动了一个使用二次幂哈希表的项目,即哈希表,其中内部表的大小为8,16,32等.有一个很好的理由支持素数大小,但那里对于两种尺寸的功率也是一些优点.

而且它非常糟糕.经过一些实验和研究后,我开始用以下方法重新哈希哈希:

public static int ReHash(int source)
{
  unchecked
  {
    ulong c = 0xDEADBEEFDEADBEEF + (ulong)source;
    ulong d = 0xE2ADBEEFDEADBEEF ^ c;
    ulong a = d += c = c << 15 | c >> -15;
    ulong b = a += d = d << 52 | d >> -52;
    c ^= b += a = a << 26 | a >> -26;
    d ^= c += b = b << 51 | b >> -51;
    a ^= d += c = c << 28 | c >> -28;
    b ^= a += d = d << 9 | d >> -9;
    c ^= b += a = a << 47 | a >> -47;
    d ^= c += b << 54 | b >> -54;
    a ^= d += c << 32 | c >> 32;
    a += d << 25 | d >> -25;
    return (int)(a >> 1);
  }
}

然后我的二次幂哈希表再也没有了.

这让我感到不安,因为上述情况不应该奏效.或者更准确地说,除非原件GetHashCode()以非常特殊的方式变差,否则它不应该起作用.

重新混合哈希码不能改进很好的哈希码,因为唯一可能的效果是我们引入了一些冲突.

重新混合哈希码不能改善可怕的哈希码,因为唯一可能的效果是我们将例如值53上的大量冲突更改为大量值18,3487,291.

重新混合哈希码只能改进一个哈希码,它至少可以很好地避免整个范围内的绝对冲突(2 32个可能的值),但是在模块化以便在哈希表中实际使用时避免冲突.虽然两个幂表的简单模数使得这一点变得更加明显,但它对更常见的素数表也有负面影响,但这并不是那么明显(重新划分的额外工作将超过收益) ,但好处仍然存在).

编辑:我也使用开放寻址,这也会增加对碰撞的敏感度,也许比它是二次幂的事实更多.

好吧,令人不安的是string.GetHashCode(),.NET(或此处的研究)中的实现可以通过这种方式进行多少改进(由于冲突更少,测试运行速度提高了大约20-30倍),并且我自己的哈希代码更加令人不安可以改进(远不止于此).

我过去编写的所有GetHashCode()实现,实际上都是在这个网站上作为答案的基础,比我经常要糟糕得多.大部分时间它对于大部分用途都"足够好",但我想要更好的东西.

所以我把这个项目放在一边(无论如何它都是一个宠物项目)并开始研究如何在.NET中快速生成一个好的,分布均匀的哈希代码.

最后,我决定将SpookyHash移植到.NET.实际上,上面的代码是使用SpookyHash从32位输入产生32位输出的快速路径版本.

现在,SpookyHash不是一个很好的快速记住代码片段.我的端口更是如此,因为我为了更好的速度而手动插入了大量的内容*.但这就是代码重用的目的.

然后我将该项目放在一边,因为正如原始项目产生了如何生成更好的哈希代码的问题,因此该项目产生了如何生成更好的.NET memcpy的问题.

然后我回来了,产生了很多重载,可以很容易地将所有原生类型(除了decimal†)都输入到哈希码中.

它的速度很快,鲍勃詹金斯应该获得大部分功劳,因为我移植的原始代码更快,特别是在64位机器上,该算法针对‡进行了优化.

完整的代码可以在https://bitbucket.org/JonHanna/spookilysharp/src上看到,但请考虑上面的代码是它的简化版本.

但是,由于它现在已经编写,因此可以更容易地使用它:

public override int GetHashCode()
{
  var hash = new SpookyHash();
  hash.Update(field1);
  hash.Update(field2);
  hash.Update(field3);
  return hash.Final().GetHashCode();
}

它还需要种子值,因此如果您需要处理不受信任的输入并希望防止Hash DoS攻击,您可以根据正常运行时间或类似情况设置种子,并使攻击者无法预测结果:

private static long hashSeed0 = Environment.TickCount;
private static long hashSeed1 = DateTime.Now.Ticks;
public override int GetHashCode()
{
  //produce different hashes ever time this application is restarted
  //but remain consistent in each run, so attackers have a harder time
  //DoSing the hash tables.
  var hash = new SpookyHash(hashSeed0, hashSeed1);
  hash.Update(field1);
  hash.Update(field2);
  hash.Update(field3);
  return hash.Final().GetHashCode();
}

*令人惊讶的是,手动内联旋转方法可以返回(x << n) | (x >> -n)改进的东西.我本可以肯定的是,对于我而言,抖动会内联,但是分析显示不然.

decimal从.NET的角度来看,† 不是原生的,尽管它来自C#.它的问题在于它自己GetHashCode()将精度视为重要而其本身Equals()则不然.两者都是有效的选择,但不是那样的混合.在实现您自己的版本时,您需要选择一个或另一个,但我不知道您想要哪个.

‡通过比较.如果在字符串上使用,64位的SpookyHash比string.GetHashCode()32位快得多string.GetHashCode(),这比64位略快,这比32位的SpookyHash要快得多,尽管仍然足够快,是一个合理的选择.



8> Magnus..:

这个不错:

/// 
/// Helper class for generating hash codes suitable 
/// for use in hashing algorithms and data structures like a hash table. 
/// 
public static class HashCodeHelper
{
    private static int GetHashCodeInternal(int key1, int key2)
    {
        unchecked
        {
           var num = 0x7e53a269;
           num = (-1521134295 * num) + key1;
           num += (num << 10);
           num ^= (num >> 6);

           num = ((-1521134295 * num) + key2);
           num += (num << 10);
           num ^= (num >> 6);

           return num;
        }
    }

    /// 
    /// Returns a hash code for the specified objects
    /// 
    /// An array of objects used for generating the 
    /// hash code.
    /// 
    /// A hash code, suitable for use in hashing algorithms and data 
    /// structures like a hash table. 
    /// 
    public static int GetHashCode(params object[] arr)
    {
        int hash = 0;
        foreach (var item in arr)
            hash = GetHashCodeInternal(hash, item.GetHashCode());
        return hash;
    }

    /// 
    /// Returns a hash code for the specified objects
    /// 
    /// The first object.
    /// The second object.
    /// The third object.
    /// The fourth object.
    /// 
    /// A hash code, suitable for use in hashing algorithms and
    /// data structures like a hash table.
    /// 
    public static int GetHashCode(T1 obj1, T2 obj2, T3 obj3,
        T4 obj4)
    {
        return GetHashCode(obj1, GetHashCode(obj2, obj3, obj4));
    }

    /// 
    /// Returns a hash code for the specified objects
    /// 
    /// The first object.
    /// The second object.
    /// The third object.
    /// 
    /// A hash code, suitable for use in hashing algorithms and data 
    /// structures like a hash table. 
    /// 
    public static int GetHashCode(T1 obj1, T2 obj2, T3 obj3)
    {
        return GetHashCode(obj1, GetHashCode(obj2, obj3));
    }

    /// 
    /// Returns a hash code for the specified objects
    /// 
    /// The first object.
    /// The second object.
    /// 
    /// A hash code, suitable for use in hashing algorithms and data 
    /// structures like a hash table. 
    /// 
    public static int GetHashCode(T1 obj1, T2 obj2)
    {
        return GetHashCodeInternal(obj1.GetHashCode(), obj2.GetHashCode());
    }
}

以下是如何使用它:

private struct Key
{
    private Type _type;
    private string _field;

    public Type Type { get { return _type; } }
    public string Field { get { return _field; } }

    public Key(Type type, string field)
    {
        _type = type;
        _field = field;
    }

    public override int GetHashCode()
    {
        return HashCodeHelper.GetHashCode(_field, _type);
    }

    public override bool Equals(object obj)
    {
        if (!(obj is Key))
            return false;
        var tf = (Key)obj;
        return tf._field.Equals(_field) && tf._type.Equals(_type);
    }
}


@Magnus,你能解释一下*为什么*这是一个好的?
当你使用object而不是泛型时,你会得到拳击和内存分配,这是GetHashCode中你不想要的.因此,泛型是可行的方法.

9> Muhammad Reh..:
.NET Core 2.1及更高版本

如果使用的是.NET Core 2.1或更高版本,则可以使用System.HashCode结构。有两种使用方法:

HashCode.Combine

Combine方法可用于创建哈希码,最多提供8个对象。

public override int GetHashCode() => HashCode.Combine(this.object1, this.object2);

HashCode.Add

Add方法可帮助您处理集合:

public override int GetHashCode()
{
    var hashCode = new HashCode();
    hashCode.Add(this.object1);
    foreach (var item in this.collection)
    {
        hashCode.Add(item);
    }
    return hashCode.ToHashCode();
}
GetHashCode变得简单

您可以阅读完整的博客文章“ GetHashCode Made Easy ”,以获取更多详细信息和评论。

使用范例

public class SuperHero
{
    public int Age { get; set; }
    public string Name { get; set; }
    public List Powers { get; set; }

    public override int GetHashCode() =>
        HashCode.Of(this.Name).And(this.Age).AndEach(this.Powers);
}

实作

public struct HashCode : IEquatable
{
    private const int EmptyCollectionPrimeNumber = 19;
    private readonly int value;

    private HashCode(int value) => this.value = value;

    public static implicit operator int(HashCode hashCode) => hashCode.value;

    public static bool operator ==(HashCode left, HashCode right) => left.Equals(right);

    public static bool operator !=(HashCode left, HashCode right) => !(left == right);

    public static HashCode Of(T item) => new HashCode(GetHashCode(item));

    public static HashCode OfEach(IEnumerable items) =>
        items == null ? new HashCode(0) : new HashCode(GetHashCode(items, 0));

    public HashCode And(T item) => 
        new HashCode(CombineHashCodes(this.value, GetHashCode(item)));

    public HashCode AndEach(IEnumerable items)
    {
        if (items == null)
        {
            return new HashCode(this.value);
        }

        return new HashCode(GetHashCode(items, this.value));
    }

    public bool Equals(HashCode other) => this.value.Equals(other.value);

    public override bool Equals(object obj)
    {
        if (obj is HashCode)
        {
            return this.Equals((HashCode)obj);
        }

        return false;
    }

    public override int GetHashCode() => this.value.GetHashCode();

    private static int CombineHashCodes(int h1, int h2)
    {
        unchecked
        {
            // Code copied from System.Tuple a good way to combine hashes.
            return ((h1 << 5) + h1) ^ h2;
        }
    }

    private static int GetHashCode(T item) => item?.GetHashCode() ?? 0;

    private static int GetHashCode(IEnumerable items, int startHashCode)
    {
        var temp = startHashCode;

        var enumerator = items.GetEnumerator();
        if (enumerator.MoveNext())
        {
            temp = CombineHashCodes(temp, GetHashCode(enumerator.Current));

            while (enumerator.MoveNext())
            {
                temp = CombineHashCodes(temp, GetHashCode(enumerator.Current));
            }
        }
        else
        {
            temp = CombineHashCodes(temp, EmptyCollectionPrimeNumber);
        }

        return temp;
    }
}



10> James Ko..:

从https://github.com/dotnet/coreclr/pull/14863开始,有一种新方法可以生成超级简单的哈希码!写吧

public override int GetHashCode()
    => HashCode.Combine(field1, field2, field3);

这将生成质量哈希代码,而无需担心实现细节.



11> Scott Wegner..:

这是Jon Skeet上面发布的算法的另一个流畅实现,但不包括分配或装箱操作:

public static class Hash
{
    public const int Base = 17;

    public static int HashObject(this int hash, object obj)
    {
        unchecked { return hash * 23 + (obj == null ? 0 : obj.GetHashCode()); }
    }

    public static int HashValue(this int hash, T value)
        where T : struct
    {
        unchecked { return hash * 23 + value.GetHashCode(); }
    }
}

用法:

public class MyType
{
    public string Name { get; set; }

    public string Description { get; set; }

    public int Value { get; set; }

    public IEnumerable Children { get; set; }

    public override int GetHashCode()
    {
        return Hash.Base
            .HashObject(this.Name)
            .HashObject(this.Description)
            .HashValue(this.Value)
            .HashObject(this.Children);
    }
}

HashValue由于泛型类型约束,编译器将确保不使用类调用.但是没有编译器支持,HashObject因为添加泛型参数也会添加装箱操作.



12> bitbonk..:

这是我简单化的方法.我正在使用经典的构建器模式.它是类型安全(没有装箱/拆箱),也适用于.NET 2.0(没有扩展方法等).

它是这样使用的:

public override int GetHashCode()
{
    HashBuilder b = new HashBuilder();
    b.AddItems(this.member1, this.member2, this.member3);
    return b.Result;
} 

这是真正的建设者类:

internal class HashBuilder
{
    private const int Prime1 = 17;
    private const int Prime2 = 23;
    private int result = Prime1;

    public HashBuilder()
    {
    }

    public HashBuilder(int startHash)
    {
        this.result = startHash;
    }

    public int Result
    {
        get
        {
            return this.result;
        }
    }

    public void AddItem(T item)
    {
        unchecked
        {
            this.result = this.result * Prime2 + item.GetHashCode();
        }
    }

    public void AddItems(T1 item1, T2 item2)
    {
        this.AddItem(item1);
        this.AddItem(item2);
    }

    public void AddItems(T1 item1, T2 item2, T3 item3)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
    }

    public void AddItems(T1 item1, T2 item2, T3 item3, 
        T4 item4)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
        this.AddItem(item4);
    }

    public void AddItems(T1 item1, T2 item2, T3 item3, 
        T4 item4, T5 item5)
    {
        this.AddItem(item1);
        this.AddItem(item2);
        this.AddItem(item3);
        this.AddItem(item4);
        this.AddItem(item5);
    }        

    public void AddItems(params T[] items)
    {
        foreach (T item in items)
        {
            this.AddItem(item);
        }
    }
}



13> Charles Burn..:

ReSharper用户可以使用生成GetHashCode,等于等ReSharper -> Edit -> Generate Code -> Equality Members

// ReSharper's GetHashCode looks like this
public override int GetHashCode() {
    unchecked {
        int hashCode = Id;
        hashCode = (hashCode * 397) ^ IntMember;
        hashCode = (hashCode * 397) ^ OtherIntMember;
        hashCode = (hashCode * 397) ^ (RefMember != null ? RefMember.GetHashCode() : 0);
        // ...
        return hashCode;
    }
}

推荐阅读
个性2402852463
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有