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方法,则返回值不太可能适合用作散列表中的键。 。
像byte
, short
, int
, long
, char
和string
这样的基本数据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,您可以基于内部值而不是对象引用来报告相等性。