Object.GetHashCode()的默认实现

GetHashCode()的默认实现如何工作? 它是否有效和足够好地处理结构,类,数组等等?

我试图决定在什么情况下我应该收拾我自己,在什么情况下我可以安全地依靠默认实现来做好。 如果可能的话,我不想重新发明轮子。

 namespace System { public class Object { [MethodImpl(MethodImplOptions.InternalCall)] internal static extern int InternalGetHashCode(object obj); public virtual int GetHashCode() { return InternalGetHashCode(this); } } } 

InternalGetHashCode映射到CLR中的ObjectNative :: GetHashCode函数,如下所示:

 FCIMPL1(INT32, ObjectNative::GetHashCode, Object* obj) { CONTRACTL { THROWS; DISABLED(GC_NOTRIGGER); INJECT_FAULT(FCThrow(kOutOfMemoryException);); MODE_COOPERATIVE; SO_TOLERANT; } CONTRACTL_END; VALIDATEOBJECTREF(obj); DWORD idx = 0; if (obj == 0) return 0; OBJECTREF objRef(obj); HELPER_METHOD_FRAME_BEGIN_RET_1(objRef); // Set up a frame idx = GetHashCodeEx(OBJECTREFToObject(objRef)); HELPER_METHOD_FRAME_END(); return idx; } FCIMPLEND 

GetHashCodeEx的完整实现是相当大的,所以更容易链接到C ++源代码 。

对于一个类,默认是基本上引用相等,这通常很好。 如果编写一个结构体,那么覆盖相等性(更重要的是为了避免装箱)是比较常见的,但是不pipe怎样,你写一个结构体是非常罕见的!

当重写相等时,你应该总是有一个匹配的Equals()GetHashCode() (即两个值,如果Equals()返回true,他们必须返回相同的散列码,但反过来是不需要的) – 这是常见的还要提供== / !=运算符,并经常实现IEquatable<T>

为了生成哈希码,通常使用分解总和,因为这样可以避免配对值的冲突 – 例如,基本的2字段哈希:

 unchecked // disable overflow, for the unlikely possibility that you { // are compiling with overflow-checking enabled int hash = 27; hash = (13 * hash) + field1.GetHashCode(); hash = (13 * hash) + field2.GetHashCode(); return hash; } 

这具有以下优点:

  • {1,2}的散列不同于{2,1}的散列
  • {1,1}的散列与{2,2}的散列不一样

等 – 如果只是使用一个不加权的总和,或者xor( ^ )等,这可能是常见的。

Object的GetHashCode方法的文档说: “此方法的默认实现不能用作散列目的的唯一对象标识符”。 而ValueType的说法是: “如果调用派生types的GetHashCode方法,则返回值不太可能适合用作散列表中的键。

byteshortintlongcharstring这样的基本数据types实现了一个很好的GetHashCode方法。 其他一些类和结构,比如Point ,实现了一个GetHashCode方法,它可能适用于您的特定需求,也可能不适合您。 你只需要尝试一下,看看它是否足够好。

每个类或结构的文档可以告诉你它是否覆盖默认的实现。 如果它不覆盖它,你应该使用你自己的实现。 对于您需要使用GetHashCode方法创build的任何类或结构,您应该使用适当的成员自己的实现计算哈希代码。

一般来说,如果你正在覆盖Equals,你想覆盖GetHashCode。 原因是因为两者都用来比较你的类/结构的平等。

在检查Foo A,B时使用等于;

如果(A == B)

既然我们知道指针不可能匹配,我们可以比较内部成员。

 Equals(obj o) { if (o == null) return false; MyType Foo = o as MyType; if (Foo == null) return false; if (Foo.Prop1 != this.Prop1) return false; return Foo.Prop2 == this.Prop2; } 

GetHashCode通常由散列表使用。 由类生成的哈希码应该始终与给出状态的类相同。

我通常这样做,

 GetHashCode() { int HashCode = this.GetType().ToString().GetHashCode(); HashCode ^= this.Prop1.GetHashCode(); etc. return HashCode; } 

有人会说,哈希码应该只计算一次对象的生命周期,但我不同意这一点(我可能是错的)。

使用对象提供的默认实现,除非你有一个类的引用,它们将不会相等。 通过覆盖Equals和GetHashCode,您可以基于内部值而不是对象引用来报告相等性。