GetHashCode覆盖包含generics数组的对象
我有一个包含以下两个属性的类:
public int Id { get; private set; } public T[] Values { get; private set; }
我已经使IEquatable<T>
并覆盖IEquatable<T>
像这样:
public override bool Equals(object obj) { return Equals(obj as SimpleTableRow<T>); } public bool Equals(SimpleTableRow<T> other) { // Check for null if(ReferenceEquals(other, null)) return false; // Check for same reference if(ReferenceEquals(this, other)) return true; // Check for same Id and same Values return Id == other.Id && Values.SequenceEqual(other.Values); }
当有覆盖object.Equals
我也必须重写GetHashCode
当然。 但是我应该实现什么代码? 如何创build一个通用数组的哈希码? 我怎么把它与Id
整数?
public override int GetHashCode() { return // What? }
由于在这个线程中提出的问题,我发布了另一个答复,显示如果发生错误,会发生什么……主要是,您不能使用数组的GetHashCode()
; 正确的行为是当您运行它时不会显示警告…切换注释以修复它:
using System; using System.Collections.Generic; using System.Linq; static class Program { static void Main() { // first and second are logically equivalent SimpleTableRow<int> first = new SimpleTableRow<int>(1, 2, 3, 4, 5, 6), second = new SimpleTableRow<int>(1, 2, 3, 4, 5, 6); if (first.Equals(second) && first.GetHashCode() != second.GetHashCode()) { // proven Equals, but GetHashCode() disagrees Console.WriteLine("We have a problem"); } HashSet<SimpleTableRow<int>> set = new HashSet<SimpleTableRow<int>>(); set.Add(first); set.Add(second); // which confuses anything that uses hash algorithms if (set.Count != 1) Console.WriteLine("Yup, very bad indeed"); } } class SimpleTableRow<T> : IEquatable<SimpleTableRow<T>> { public SimpleTableRow(int id, params T[] values) { this.Id = id; this.Values = values; } public int Id { get; private set; } public T[] Values { get; private set; } public override int GetHashCode() // wrong { return Id.GetHashCode() ^ Values.GetHashCode(); } /* public override int GetHashCode() // right { int hash = Id; if (Values != null) { hash = (hash * 17) + Values.Length; foreach (T t in Values) { hash *= 17; if (t != null) hash = hash + t.GetHashCode(); } } return hash; } */ public override bool Equals(object obj) { return Equals(obj as SimpleTableRow<T>); } public bool Equals(SimpleTableRow<T> other) { // Check for null if (ReferenceEquals(other, null)) return false; // Check for same reference if (ReferenceEquals(this, other)) return true; // Check for same Id and same Values return Id == other.Id && Values.SequenceEqual(other.Values); } }
FWIW,在哈希代码中使用值的内容是非常危险的。 你只能这样做,如果你能保证它永远不会改变。 但是,由于它是暴露的,我不认为保证是可能的。 对象的哈希码不应该改变。 否则,它将失去作为Hashtable或Dictionary中键的价值。 考虑在Hashtable中使用对象作为键的难以发现的错误,其哈希码由于外部影响而改变,并且您不能再在Hashtable中find它!
由于hashCode是一个存储对象(lllike在哈希表中)的关键,我只会使用Id.GetHashCode()
感觉如何?
public override int GetHashCode() { int hash = Id; if (Values != null) { hash = (hash * 17) + Values.Length; foreach (T t in Values) { hash *= 17; if (t != null) hash = hash + t.GetHashCode(); } } return hash; }
这应该与SequenceEqual
兼容,而不是在数组上进行参考比较。
public override int GetHashCode() { return Id.GetHashCode() ^ Values.GetHashCode(); }
评论和其他答案中有几个好点。 如果对象被用作字典中的键,那么OP应该考虑是否将值用作“键”的一部分。 如果是这样,那么他们应该是散列码的一部分,否则,不。
另一方面,我不知道为什么GetHashCode方法应该镜像SequenceEqual。 这是为了计算一个哈希表中的索引,而不是完整的等式决定因素。 如果使用上述algorithm存在许多散列表冲突,并且如果它们的值序列不同,则应该select考虑序列的algorithm。 如果顺序并不重要,请节省时间,不要考虑在内。
我会这样做:
long result = Id.GetHashCode(); foreach(T val in Values) result ^= val.GetHashCode(); return result;
我知道这个线程很旧,但是我写了这个方法来让我计算多个对象的hashcode。 这对于这种情况非常有帮助。 这不是完美的,但它确实满足我的需求,也很可能是你的需求。
我真的不能相信它。 我从一些.net gethashcode实现中获得了这个概念。 我正在使用419(毕竟,这是我最喜欢的大素数),但是你可以select任何合理的素数(不能太小……不要太大)。
所以,这里是我如何得到我的hashcode:
using System.Collections.Generic; using System.Linq; public static class HashCodeCalculator { public static int CalculateHashCode(params object[] args) { return args.CalculateHashCode(); } public static int CalculateHashCode(this IEnumerable<object> args) { if (args == null) return new object().GetHashCode(); unchecked { return args.Aggregate(0, (current, next) => (current*419) ^ (next ?? new object()).GetHashCode()); } } }
如果Id和值永远不会更改,并且值不为空…
public override int GetHashCode() { return Id ^ Values.GetHashCode(); }
请注意,您的类不是不可变的,因为任何人都可以修改值的内容,因为它是一个数组。 鉴于此,我不会尝试使用其内容生成哈希码。
我只需要添加另一个答案,因为没有提到更明显的(也是最容易实现的)解决scheme之一 – 不包括您的GetHashCode
计算中的集合!
这里似乎已经忘记的主要事情是GetHashCode
结果的唯一性不是必需的(甚至在许多情况下甚至是可能的)。 不等的对象不必返回不相等的散列码,唯一的要求是相同的对象返回相同的散列码。 所以通过这个定义,下面的GetHashCode
实现对于所有对象都是正确的(假设有一个正确的Equals
实现):
public override int GetHashCode() { return 42; }
当然这会产生散列表查找中最差的性能,O(n)而不是O(1),但是它在function上仍然是正确的。
考虑到这一点,当为一个碰巧有一个或多个成员集合的对象实现GetHashCode
时,我的一般build议是简单地忽略它们,并根据其他标量成员完全计算GetHashCode
。 除非你在哈希表中放置了大量的对象,其中所有的标量成员都具有相同的值,导致相同的哈希代码,否则这将工作得很好。
计算哈希代码时忽略集合成员也可以提高性能,尽pipe散列代码值的分布减less了。 请记住,使用散列码可以提高哈希表中的性能,因为不需要调用Equals
N次,而只需要调用一次GetHashCode和一个快速哈希表查找。 如果每个对象都有一个内部数组,其中有10000个条目都参与哈希代码的计算,那么良好分配所带来的好处就可能会丢失。 如果生成代码的代价要低得多,那么分布式散列代码的分布就会less一些。