我正在玩“词典”并偶然发现了下面的情景
public class MyObject
{
public string I { get; set; }
public string J { get; set; }
public string K { get; set; }
public override int GetHashCode()
{
int hashCode = (I+J+K).GetHashCode();
Debugger.Log(9, "INFO", hashCode.ToString() + System.Environment.NewLine);
return hashCode;
}
}
class Program
{
static void Main(string[] args)
{
MyObject obj1 = new MyObject() { I = "Hello", J = "World" };
MyObject obj2 = new MyObject() { I = "Hello", J = "World" };
Dictionary<MyObject, string> collection = new Dictionary<MyObject, string>();
collection.Add(obj1, "1");
var result = collection[obj2]; // KeyNotFound exception here.
}
}
我有MyObject类作为字典的键,我重写GetHashCode方法,根据存储在类中的值返回哈希码。
因此,当执行上面的代码时,obj1和obj2都返回相同的哈希码,但是字典仍会抛出KeyNotFound异常。
出现这种行为的原因是什么?
答案 0 :(得分:7)
在.NET中,GetHashCode
与Equals
方法一起使用,以确定与集合中的存储有关的对象。
请注意,哈希表比通过哈希码简单地将密钥映射到单个槽更复杂。由于哈希码的性质,可能会发生冲突并且做在实践中发生(尽管具有良好的哈希函数,这不应该经常发生)。因此,大多数散列表实现必须处理生成相同散列码的两个不同对象的情况,并且这通常通过散列表中的每个“时隙”处的链表来实现。哈希码用于确定槽,Equals
方法用于确定对象存储在链表中的位置(在哈希表的大多数“标准”实现中)。
然而,一句警告:很少有充分的理由来覆盖GetHashCode
的内置行为。我发现这个有趣的SO帖子讨论GetHashCode
和Equals
,值得一读:Why is it important to override GetHashCode when Equals method is overridden?。它讨论了改变行为的优点/缺点,好的和坏的散列函数的属性,这两种方法所需的属性以及其他好处。
答案 1 :(得分:3)
您需要覆盖Object.Equals
。
Dictionary<TKey, TValue>
和其他基于散列的集合将散列等式视为必需但 条件不足以完全相等,因为散列冲突的可能性。在您的示例中,key-getter找到要搜索的正确哈希桶,甚至将obj1
视为完全相等的候选者,但因为Equals
的默认实现基于引用相等,它被拒绝了。
理想情况下,在您的课程上实施IEquatable<T>
:
public class MyObject : IEquatable<MyObject>
{
public string I { get; set; }
public string J { get; set; }
public string K { get; set; }
public override int GetHashCode()
{
// you might want to consider a better hash-function here.
return (I + J + K).GetHashCode();
}
public override bool Equals(object obj)
{
return base.Equals(obj as MyObject);
}
public bool Equals(MyObject other)
{
return other != null && other.I == I && other.J == J && other.K == K;
}
}
还要记住,只要键字对象存在于字典中,它就不能改变。