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

如果实现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); } 

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

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

有些人使用:

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

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

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

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

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

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

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

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

为了完整起见,这里是从.NET Tuple Reference源第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)); } 

当然,实际的Tuple GetHashCode() (实际上是一个Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer) )有一个很大的switch块来决定根据它持有多少项来调用哪一个 - 你自己的代码可能赢了不需要。

以下是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); } 
 public override GetHashCode() { return this.Field1.GetHashCode() | this.Field2.GetHashCode | this.Field3.GetHashCode(); }