好吧,任何实现GetHashCode()
. 这些当然是我们在实现自己的时候权衡的事情,但是在这种情况下ValueType.GetHashCode()
存在一个特别的困难,因为他们没有太多关于具体类型的实际细节的信息。当然,当我们创建一个抽象类或一个旨在成为类的基础的抽象类时,这经常发生在我们身上,这些类将在状态方面增加更多,但在这些情况下,我们有一个明显的解决方案,即只使用默认实现object.GetHashCode()
除非派生类愿意在那里重写它。
由于ValueType.GetHashCode()
他们没有这种奢侈,因为值类型和引用类型之间的主要区别在于,尽管谈论堆栈与堆的实现细节很受欢迎,但对于值类型等价与值相关的事实,而对于一个对象类型等价与身份相关(即使对象通过覆盖定义了不同形式的等价,Equals()
并且GetHashCode()
引用相等的概念仍然存在并且仍然有用。
因此,对于该Equals()
方法,实现是显而易见的;检查这两个对象的类型是否相同,如果是,则还要检查所有字段是否相等(实际上有一个优化,在某些情况下会进行按位比较,但这是基于相同基本思想的优化)。
做什么GetHashCode()
? 根本没有完美的解决方案。他们可以做的一件事是在每个字段上进行某种 mult-then-add 或 shift-then-xor。这可能会给出一个非常好的哈希码,但如果有很多字段可能会很昂贵(不要介意不建议使用具有很多字段的值类型,实现者必须考虑它们仍然可以,而且确实如此甚至有时它是有意义的,尽管老实说我无法想象它既有意义又对它进行散列也有意义的时候)。如果他们知道实例之间的某些字段很少有差异,他们可以忽略这些字段并且仍然具有相当好的哈希码,同时也非常快。最后,他们可以忽略大多数字段,并希望他们不会忽略的字段在大多数情况下的价值是不同的。
(当没有实例字段时做什么是另一回事,也是一个很好的选择,这样的值类型等于所有其他相同类型的实例,并且它们有一个与之匹配的哈希码)。
因此,如果您在第一个字段相同(或返回相同哈希码)的情况下对大量值进行散列处理,这是一个很糟糕的实现,但在其他情况下其他实现会很糟糕(Mono 将所有字段的哈希码异或在一起,在你的情况下更好,在其他情况下更糟)。
更改字段顺序的问题并不重要,因为哈希码已明确说明仅在进程的生命周期内保持有效,并且不适用于大多数情况下它们可以持续存在的情况(在某些缓存情况下可能很有用如果在代码更改后找不到正确的东西,也没有什么坏处)。
所以,不是很好,但没有什么是完美的。它表明,当使用对象作为键时,必须始终考虑“平等”意味着什么的两面。在您的情况下,它很容易通过以下方式修复:
public class KVPCmp<TKey, TValue> : IEqualityComparer<KeyValuePair<TKey, TValue>>, IEqualityComparer
{
bool IEqualityComparer.Equals(object x, object y)
{
if(x == null)
return y == null;
if(y == null)
return false;
if(!(x is KeyValuePair<TKey, TValue>) || !(y is KeyValuePair<TKey, TValue>))
throw new ArgumentException("Comparison of KeyValuePairs only.");
return Equals((KeyValuePair<TKey, TValue>) x, (KeyValuePair<TKey, TValue>) y);
}
public bool Equals(KeyValuePair<TKey, TValue> x, KeyValuePair<TKey, TValue> y)
{
return x.Key.Equals(y.Key) && x.Value.Equals(y.Value);
}
public int GetHashCode(KeyValuePair<TKey, TValue> obj)
{
int keyHash = obj.GetHashCode();
return ((keyHash << 16) | (keyHash >> 16)) ^ obj.Value.GetHashCode();
}
public int GetHashCode(object obj)
{
if(obj == null)
return 0;
if(!(obj is KeyValuePair<TKey, TValue>))
throw new ArgumentException();
return GetHashCode((KeyValuePair<TKey, TValue>)obj);
}
}
在创建字典时使用它作为比较器,一切都应该很好(你只需要真正的通用比较器方法,但将其余的留在没有害处,有时会有用)。