我有一个包含以下两个属性的类:
public int Id { get; private set; }
public T[] Values { get; private set; }
我已将其设为IEquatable<T>
并覆盖object.Equals
,如下所示:
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
。但是我应该实现什么代码?如何从通用数组中创建哈希码?如何将其与Id
整数组合?
public override int GetHashCode()
{
return // What?
}
答案 0 :(得分:81)
由于这个帖子中出现的问题,我发布了另一个回复,显示如果你弄错了会发生什么......主要是你不能使用数组的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);
}
}
答案 1 :(得分:30)
FWIW,在哈希码中使用值的内容非常危险。如果您能保证永远不会改变,那么您应该这样做。但是,由于它暴露,我不认为保证它是可能的。对象的哈希码永远不会改变。否则,它将作为Hashtable或Dictionary中的键丢失其值。考虑使用对象作为Hashtable中的键的难以发现的错误,其哈希码因外部影响而发生变化,您无法再在Hashtable中找到它!
答案 2 :(得分:4)
由于hashCode有点存储对象的密钥(lleeke在哈希表中),我只使用Id.GetHashCode()
答案 3 :(得分:2)
如下:
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
兼容,而不是在数组上进行参考比较。
答案 4 :(得分:1)
public override int GetHashCode() {
return Id.GetHashCode() ^ Values.GetHashCode();
}
评论和其他答案中有几点好处。如果对象用作字典中的键,则OP应考虑值是否将用作“键”的一部分。如果是这样,那么它们应该是哈希码的一部分,否则就不是。
另一方面,我不确定为什么GetHashCode方法应该镜像SequenceEqual。它意味着计算哈希表的索引,而不是完全相等的决定因素。如果使用上述算法存在许多哈希表冲突,并且如果它们在值的序列中不同,则应选择考虑序列的算法。如果序列无关紧要,请节省时间,不要将其考虑在内。
答案 5 :(得分:1)
我只需要添加另一个答案,因为没有提到一个更明显(并且最容易实现)的解决方案 - 不包括GetHashCode
计算中的集合!
这里似乎忘记的主要事情是GetHashCode
的结果的唯一性不是必需的(或者在许多情况下甚至是可能的)。不等的对象不必返回不等的哈希码,唯一的要求是等对象返回相等的哈希码。因此,根据该定义,GetHashCode
的以下实现对于所有对象都是正确的(假设有正确的Equals
实现):
public override int GetHashCode()
{
return 42;
}
当然这会产生散列表查找中最差的性能,O(n)而不是O(1),但它仍然在功能上是正确的。
考虑到这一点,我对一个碰巧拥有任何类型集合作为其一个或多个成员的对象实施GetHashCode
时的一般建议是简单地忽略它们并仅基于GetHashCode
计算在其他标量成员上。这可以很好地工作,除非你在哈希表中放入大量的对象,其中所有的标量成员具有相同的值,从而产生相同的哈希码。
在计算哈希码时忽略集合成员也可以提高性能,尽管哈希码值的分布减少了。请记住,使用哈希代码可以提高哈希表的性能,不需要调用Equals
N次,而只需要调用一次GetHashCode和快速哈希表查找。如果每个对象都有一个包含10,000个项目的内部数组,这些项目都参与哈希码的计算,那么良好分布所带来的任何好处都可能会丢失。 如果生成它的成本要低得多,那么使用稍微分散的哈希代码会更好。
答案 6 :(得分:0)
我会这样做:
long result = Id.GetHashCode();
foreach(T val in Values)
result ^= val.GetHashCode();
return result;
答案 7 :(得分:0)
如果Id和Values永远不会改变,并且Values不为null ......
public override int GetHashCode()
{
return Id ^ Values.GetHashCode();
}
请注意,您的类不是不可变的,因为任何人都可以修改Values的内容,因为它是一个数组。鉴于此,我不会尝试使用其内容生成哈希码。
答案 8 :(得分:0)
我知道这个线程已经很老了,但是我写了这个方法来允许我计算多个对象的哈希码。对于这种情况,它非常有用。它并不完美,但它确实满足了我的需求,而且很可能也符合你的需求。
我真的不能相信它。我从一些.net gethashcode实现中得到了这个概念。我正在使用419(毕竟,这是我最喜欢的大素数),但你可以选择任何合理的素数(不是太小......不是太大)。
所以,这是我如何得到我的哈希码:
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());
}
}
}