作者:夏晶阳--艺术 | 2023-08-28 13:36
默认实现如何GetHashCode()
工作?它是否有效且足够好地处理结构,类,数组等?
我试图决定在什么情况下我应该自己打包,在什么情况下我可以安全地依赖默认实现来做好.如果可能的话,我不想重新发明轮子.
1> David Brown..: 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++源代码.
为什么文档声称实现对散列不是特别有用?如果一个对象等于它自己而没有别的,任何哈希代码方法总会为给定的对象实例返回相同的值,并且通常会为不同的实例返回不同的值,问题是什么?
@ It'sNotALie.然后感谢[Archive.org](https://web.archive.org/web/20100826100454/http://www.koders.com/cpp/fid187B41425FAF5DEC8C7921012C309DC6534FE487.aspx)获取副本;-)
该文档引用必须来自非常早期的版本.在当前的MSDN文章中不再这样写,可能是因为它是错误的.
他们改变了措辞,是的,但它仍然说基本相同:"因此,不得将此方法的默认实现用作散列目的的唯一对象标识符."
@ ta.speot.is:如果您想要确定是否已将*特定实例*添加到字典中,则引用相等性是完美的.对于字符串,正如您所注意到的,一个*通常*更感兴趣的是是否已经添加了包含*相同字符序列*的字符串.这就是为什么`string`会覆盖`GetHashCode`.另一方面,假设您想要计算各种控件处理`Paint`事件的次数.您可以使用`Dictionary
`(每个`int []`存储将只包含一个项目). @ ta.speot.is:当你从正在观看的其中一个控件中获取一个Paint事件时,可以使用`MyControlCounts [Sender] [0] ++;`(或者使用`TryGetValue`的一些变体).即使控件恰好定义了某种形式的值相等,也不会是您感兴趣的内容.您需要使用引用相等性以及默认(基于引用的)哈希代码. 2> Marc Gravell..: 对于一个类,默认值基本上是引用相等,这通常很好.如果编写一个结构,更常见的是覆盖相等(尤其是避免装箱),但是你编写一个结构是非常罕见的!
当重写等式,你应该始终有一个匹配的Equals()
和GetHashCode()
(即两个值,如果Equals()
返回true,他们必须 返回相同的哈希码,但反过来不是 必需的) -这是常见的也提供==
/ !=
运营商,并经常到也实施IEquatable
.
为了生成哈希码,通常使用因式和,因为这可以避免配对值上的冲突 - 例如,对于基本的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(^
)等,这可能很常见.
@sinelaw是的,应该执行`unchecked`.幸运的是,`unchecked`是C#中的默认值,但最好将其显式化; 编辑 3> Guffa..: ObjectGetHashCode
方法的文档说"不得将此方法的默认实现用作散列目的的唯一对象标识符." ValueType的那个说"如果你调用派生类型的GetHashCode方法,返回值可能不适合用作哈希表中的键." .
基本数据类型,例如byte
,short
,int
,long
,char
以及string
实现良好的GetHashCode方法.其他一些类和结构(Point
例如,实现GetHashCode
可能适合或不适合您的特定需求的方法).你只需要试一试,看看它是否足够好.
每个类或结构的文档可以告诉您它是否覆盖默认实现.如果它没有覆盖它,你应该使用自己的实现.对于您自己创建需要使用该GetHashCode
方法的任何类或结构,您应该创建自己的实现,使用适当的成员来计算哈希代码.
@ user502144这有什么问题?它是一个完美的唯一标识符,易于计算,对平等没有误报...... 我不同意你应该*例行地*添加你自己的实现.简单地说,绝大多数类(特别是)永远不会被测试为相等 - 或者它们在哪里,内置的引用相等是好的.在(已经很少见)编写结构的时候,它会更常见,更真实.