31

Jon Skeet在此处概述了一种实现 GetHashCode 的方法(需要这样做的地方) 。重复他的代码:

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(),但它结合其各种成分的哈希码的方式存在一些严重的错误。用于组合哈希码的库函数将有助于避免此类错误。

4

5 回答 5

20

有些人使用:

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

在 MSDN 上Object.GetHashCode()提到了它,并带有警告:

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

聚合组成散列的逻辑由 提供System.Tuple,希望对此有所考虑...

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

于 2013-08-05T18:50:06.997 回答
20

编辑: 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();
}

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

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

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

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

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

老答案:

为了完整起见,这里是从.NET 元组参考源第 52 行获取的散列算法。有趣的是,这个散列算法是从 .NET 复制过来的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块来决定根据它持有的项目数量来调用其中的哪一个——您自己的代码可能不需要这样做。

于 2016-05-26T00:14:54.913 回答
10

它不完全一样,但我们在Noda TimeHashCodeHelper中有一个类(它有很多覆盖相等和哈希码操作的类型)。

它是这样使用的(取自ZonedDateTime):

public override int GetHashCode()
{
    int hash = HashCodeHelper.Initialize();
    hash = HashCodeHelper.Hash(hash, LocalInstant);
    hash = HashCodeHelper.Hash(hash, Offset);
    hash = HashCodeHelper.Hash(hash, Zone);
    return hash;
}

请注意,它是一种通用方法,可以避免值类型的装箱。它自动处理空值(使用 0 作为值)。请注意,该MakeHash方法有一个unchecked块,因为 Noda Time 使用检查算法作为项目设置,而应允许哈希码计算溢出。

于 2013-08-05T20:05:49.170 回答
1

这是 Ryan 的回答System.Web.Util.HashCodeCombiner中提到的一些简洁(尽管效率不高)的重构

    public static int CombineHashCodes(params object[] objects)
    {
        // From System.Web.Util.HashCodeCombiner
        int combine(int h1, int h2) => (((h1 << 5) + h1) ^ h2);

        return objects.Select(it => it.GetHashCode()).Aggregate(5381,combine);
    }

    public static int CombineHashCodes(IEqualityComparer comparer, params object[] objects)
    {
        // From System.Web.Util.HashCodeCombiner
        int combine(int h1, int h2) => (((h1 << 5) + h1) ^ h2);

        return objects.Select(comparer.GetHashCode).Aggregate(5381, combine);
    }
于 2017-11-30T21:23:02.070 回答
-9
public override GetHashCode()
{
    return this.Field1.GetHashCode() | this.Field2.GetHashCode | this.Field3.GetHashCode();
}
于 2014-02-27T17:57:23.697 回答