C# – 类的通用HashCode实现

我正在研究如何为类构建最好的HashCode,我看到了一些算法。 我看到了这一个: Hash Code实现 ,似乎是.NET类的HashCode方法是类似的(参见反映代码)。

所以问题是,为什么不创建上面的静态类来自动构建HashCode,只需传递我们认为是“键”的字段。

// Old version, see edit public static class HashCodeBuilder { public static int Hash(params object[] keys) { if (object.ReferenceEquals(keys, null)) { return 0; } int num = 42; checked { for (int i = 0, length = keys.Length; i < length; i++) { num += 37; if (object.ReferenceEquals(keys[i], null)) { } else if (keys[i].GetType().IsArray) { foreach (var item in (IEnumerable)keys[i]) { num += Hash(item); } } else { num += keys[i].GetHashCode(); } } } return num; } } 

并使用它像这样:

 // Old version, see edit public sealed class A : IEquatable { public A() { } public string Key1 { get; set; } public string Key2 { get; set; } public string Value { get; set; } public override bool Equals(object obj) { return this.Equals(obj as A); } public bool Equals(A other) { if(object.ReferenceEquals(other, null)) ? false : Key1 == other.Key1 && Key2 == other.Key2; } public override int GetHashCode() { return HashCodeBuilder.Hash(Key1, Key2); } } 

会更简单,总是自己的方法,不是吗? 我错过了什么?


编辑

根据所有评论,我得到以下代码:

 public static class HashCodeBuilder { public static int Hash(params object[] args) { if (args == null) { return 0; } int num = 42; unchecked { foreach(var item in args) { if (ReferenceEquals(item, null)) { } else if (item.GetType().IsArray) { foreach (var subItem in (IEnumerable)item) { num = num * 37 + Hash(subItem); } } else { num = num * 37 + item.GetHashCode(); } } } return num; } } public sealed class A : IEquatable { public A() { } public string Key1 { get; set; } public string Key2 { get; set; } public string Value { get; set; } public override bool Equals(object obj) { return this.Equals(obj as A); } public bool Equals(A other) { if(ReferenceEquals(other, null)) { return false; } else if(ReferenceEquals(this, other)) { return true; } return Key1 == other.Key1 && Key2 == other.Key2; } public override int GetHashCode() { return HashCodeBuilder.Hash(Key1, Key2); } } 

你的Equals方法被破坏 – 它假设具有相同哈希码的两个对象必然相等。 事实并非如此。

你的哈希码方法看起来很好看,但实际上可以做一些工作 – 见下文。 它意味着在任何时候调用任何值类型值创建数组,但除此之外它没关系(正如SLaks指出的那样,集合处理存在一些问题)。 您可能需要考虑编写一些通用的重载,以避免对常见情况(可能是1,2,3或4个参数)的性能损失。 您可能还想使用foreach循环而不是plain for循环,只是习惯用法。

你可以为平等做同样的事情,但它会稍微更难和更混乱。

编辑:对于哈希代码本身,您只需要添加值。 我怀疑你是在试图做这样的事情:

 int hash = 17; hash = hash * 31 + firstValue.GetHashCode(); hash = hash * 31 + secondValue.GetHashCode(); hash = hash * 31 + thirdValue.GetHashCode(); return hash; 

但是这会将哈希值乘以31,它不会增加 31.目前,对于相同的值,您的哈希码将始终返回相同的值,无论它们是否处于相同的顺序,这是不理想的。

编辑:似乎对使用的哈希码有一些混淆。 我建议任何不确定的人都会阅读Object.GetHashCode的文档,然后阅读Eric Lippert 关于哈希和平等的博客文章 。

这就是我正在使用的:

 public static class ObjectExtensions { ///  /// Simplifies correctly calculating hash codes based upon /// Jon Skeet's answer here /// http://stackoverflow.com/a/263416 ///  ///  /// Thunks that return all the members upon which /// the hash code should depend. ///  public static int CalculateHashCode(this object obj, params Func[] memberThunks) { // Overflow is okay; just wrap around unchecked { int hash = 5; foreach (var member in memberThunks) hash = hash * 29 + member().GetHashCode(); return hash; } } } 

用法示例:

 public class Exhibit { public virtual Document Document { get; set; } public virtual ExhibitType ExhibitType { get; set; } #region System.Object public override bool Equals(object obj) { return Equals(obj as Exhibit); } public bool Equals(Exhibit other) { return other != null && Document.Equals(other.Document) && ExhibitType.Equals(other.ExhibitType); } public override int GetHashCode() { return this.CalculateHashCode( () => Document, () => ExhibitType); } #endregion } 

而不是调用keys[i].GetType().IsArray ,你应该尝试将它keys[i].GetType().IsArrayIEnumerable (使用as关键字)。

您可以通过注册静态字段列表来修复Equals方法,而无需重复字段列表, 就像我在这里使用委托集合一样。
这也避免了每次调用的数组分配。

但请注意,我的代码不处理集合属性。