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
用法示例:
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().IsArray
为IEnumerable
(使用as
关键字)。
您可以通过注册静态字段列表来修复Equals
方法,而无需重复字段列表, 就像我在这里使用委托集合一样。
这也避免了每次调用的数组分配。
但请注意,我的代码不处理集合属性。