组合字段哈希码的简洁方法?

2022-09-01 08:55:17

一个是实现GetHashCode的方法 - 需要这样做 - 由Jon Skeet在这里概述。重复他的代码:

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;
    }
}

手动滚动此代码可能容易出错,并且错误可能很微妙/难以发现(您是否交换了错误?),可能很难记住不同类型的组合规则,而且我不喜欢花费精力为不同的领域和类一遍又一遍地编写/复习相同的内容。它还可以混淆重复噪声中最重要的细节之一(我记得包括所有字段吗?)。+*

有没有一种简洁的方法来使用.net库组合字段哈希码?显然,我可以自己写,但如果有一些习语/内置的东西,我宁愿这样做。

例如,在Java中(使用JDK7),我可以通过以下方式实现上述目标:

   @Override
   public int hashCode()  
   {  
      return Objects.hash(field1, field2, field3);  
   }  

这确实有助于消除错误并专注于重要细节。

动机:我遇到了一个需要重写的C#类,但是它组合其各个组成部分的哈希码的方式有一些严重的错误。用于组合哈希码的库函数对于避免此类错误非常有用。GetHashCode()


答案 1

编辑:现已发布。现在,推荐的创建哈希码的方式是这样的:System.HashCode

public override int GetHashCode()
{
    return HashCode.Combine(fieldA, fieldB, fieldC);
}

System.HashCode.Combine()将在内部调用每个字段,并自动执行正确的操作。.GetHashCode()

对于非常多的字段(超过 8 个),您可以创建一个实例,然后使用方法:HashCode.Add()

public override int GetHashCode()
{
    HashCode hash = new HashCode();
    hash.Add(fieldA);
    hash.Add(fieldB);
    hash.Add(fieldC);
    hash.Add(fieldD);
    hash.Add(fieldE);
    hash.Add(fieldF);
    hash.Add(fieldG);
    hash.Add(fieldH);
    hash.Add(fieldI);
    return hash.ToHashCode();
}

Visual Studio 2019现在有一个快速操作助手,可以为您生成Equals()GetHashCode()。只需右键单击声明中的类名>快速操作和重构>生成等于和 GetHashCode。选择希望它用于相等的成员,然后选择“实现 IEqualable”,然后单击“确定”。

最后一件事:如果您需要获取对象的结构哈希代码,例如,如果要包含根据其内容(也称为结构)而不是其引用而变化的数组的哈希代码,则需要将字段转换为并手动获取其哈希代码,如下所示:IStructuralEquatable

public override int GetHashCode()
{
    return HashCode.Combine(
        fieldA,
        ((IStructuralEquatable)stringArrayFieldB).GetHashCode(EqualityComparer<string>.Default));
}

这是因为接口几乎总是显式实现的,因此需要强制转换为调用而不是默认方法。IStructuralEquatableIStructuralEquatableIStructuralEquatable.GetHashCode()object.GetHashCode()

最后,在当前实现中,an 只是整数值本身,因此传入哈希码值而不是字段本身对结果没有影响。.GetHashCodeintHashCode.Combine()

旧答案:

为了完整起见,下面是从 .NET 元组引用源第 52 行获取的哈希算法。有趣的是,这个哈希算法是从System.Web.Util.HashCodeCombiner复制过来的。

代码如下:

public override int GetHashCode() {
    // hashing method taken from .NET Tuple reference
    // expand this out to however many items you need to hash
    return CombineHashCodes(this.item1.GetHashCode(), this.item2.GetHashCode(), this.item3.GetHashCode());
}

internal static int CombineHashCodes(int h1, int h2) {
    // this is where the magic happens
    return (((h1 << 5) + h1) ^ h2);
}

internal static int CombineHashCodes(int h1, int h2, int h3) {
    return CombineHashCodes(CombineHashCodes(h1, h2), h3);
}

internal static int CombineHashCodes(int h1, int h2, int h3, int h4) {
    return CombineHashCodes(CombineHashCodes(h1, h2), CombineHashCodes(h3, h4));
}

internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5) {
    return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), h5);
}

internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6) {
    return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), CombineHashCodes(h5, h6));
}

internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7) {
    return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), CombineHashCodes(h5, h6, h7));
}

internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7, int h8) {
    return CombineHashCodes(CombineHashCodes(h1, h2, h3, h4), CombineHashCodes(h5, h6, h7, h8));
}

当然,实际的元组(实际上是一个)有一个大块,可以根据它所包含的项目数来决定调用其中哪一个 - 你自己的代码可能不需要它。GetHashCode()Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer)switch


答案 2

有些人使用:

Tuple.Create(lastName, firstName, gender).GetHashCode()

它在 MSDN 的 Object.GetHashCode()被提及,并带有警告:

但请注意,实例化 Tuple 对象的性能开销可能会显著影响在哈希表中存储大量对象的应用程序的整体性能。

聚合组成哈希的逻辑由 提供,希望它已经有了一些想法......System.Tuple

更新:值得注意的是,@Ryan在评论中的观察结果显示,这似乎只使用了任何大小元组的最后8个元素>8。


推荐