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

时间:2013-08-05 18:34:36

标签: c# java hash

Jon Skeet here概述了实现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);  
   }  

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

动机:我遇到了一个需要覆盖GetHashCode()的C#类,但它结合各种成分的哈希码的方式有一些严重的错误。用于组合哈希码的库函数可用于避免此类错误。

5 个答案:

答案 0 :(得分:16)

有些人使用:

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

提到on MSDN at Object.GetHashCode(),警告:

  

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

聚合成分哈希的逻辑由System.Tuple提供,希望有一些想法进入...

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

答案 1 :(得分:11)

编辑:请继续关注,System.HashCode即将推出.NET Core,并将提供一种创建哈希码的独特最佳实践方法。它也将在System.Tuple和其他不可变复合类型的引擎下使用。在它发布之前,下面的答案仍然有用。

为了完整起见,这里是从.NET Tuple Reference source第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 :(得分:8)

它不完全相同,但我们在HashCodeHelper中有一个Noda Time类(它有很多类型可以覆盖相等和哈希码操作)。

它是这样使用的(取自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使用已检查算法作为项目设置,而应允许哈希码计算溢出。

答案 3 :(得分:0)

以下是Ryan {{3}}

中提到的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);
    }

答案 4 :(得分:-9)

public override GetHashCode()
{
    return this.Field1.GetHashCode() | this.Field2.GetHashCode | this.Field3.GetHashCode();
}