默认实现的 Object.GetHashCode() 方法

11 浏览
0 Comments

默认实现的 Object.GetHashCode() 方法

默认的GetHashCode()实现是如何工作的?它是否有效且足够高效地处理结构体、类、数组等?我正试图决定在哪些情况下我应该自己编写代码,在哪些情况下可以安全地依赖默认的实现。如果有可能,我不想重新发明轮子。

0
0 Comments

(Default implementation for Object.GetHashCode())这个问题的出现的原因是由于默认的结构体(struct)的 GetHashCode() 方法实现不适合用作哈希表的键,这会导致性能问题。根据 CLR 的设计,调用 System.ValueType 或 System.Enum 类型中定义的成员的每个调用可能会导致装箱分配。这就导致了实现哈希函数时面临一个难题:是使哈希函数分布良好还是使其快速。在某些情况下,可以同时实现这两者,但在 ValueType.GetHashCode() 方法中通用地实现这一点是困难的。结构体的规范哈希函数“组合”了所有字段的哈希码。但是,获取 ValueType 方法中字段的哈希码的唯一方法是使用反射。因此,CLR 的作者决定在速度和分布之间进行权衡,而默认的 GetHashCode() 版本只返回第一个非空字段的哈希码,并将其与类型 ID 结合起来。这是一种合理的行为,但不总是适用。例如,如果不幸的是,结构体的第一个字段在大多数实例中具有相同的值,那么哈希函数将始终提供相同的结果。而且,正如你可以想象的,如果这些实例存储在哈希集或哈希表中,这将导致严重的性能影响。此外,基于反射的实现速度较慢。非常慢。默认的 ValueType.Equals() 和 ValueType.GetHashCode() 都有一种特殊的优化。如果类型没有“指针”并且正确打包,那么会使用更优化的版本:GetHashCode() 方法会迭代实例并对4字节块进行异或操作,Equals() 方法使用 memcmp() 比较两个实例。但是,这种优化非常棘手。首先,很难知道何时启用优化。其次,内存比较不一定能给出正确的结果。这是一个简单的例子:-0.0 和 +0.0 是相等的,但具有不同的二进制表示。

这个问题的解决方法是,至少对于结构体,当你的自定义结构体可能被用作哈希表或字典的键时,你应该重写 Equals() 和 GetHashCode()。我还建议在这种情况下实现 IEquatable,以避免装箱。对于类来说,如果你只是写一个类,使用默认的引用相等性的哈希通常是可以的,所以在这种情况下我不会费心去重写 GetHashCode(),除非你需要重写 Equals()(那么你必须相应地重写 GetHashCode())。

0
0 Comments

默认的Object.GetHashCode()方法实现在哈希目的上并不是特别有用。这个问题的原因是,如果一个对象仅仅等于它自己,而不等于其他任何对象,那么任何总是返回相同值的哈希码方法对于给定的对象实例来说是有效的,而且通常对于不同的实例返回不同的值。但是,如果两个表示相同值的对象只有在它们是完全相同的对象时才具有相同的哈希码。例如,在使用string的情况下,如果两个字符串不是完全相同的对象,则它们的哈希码将不同。这就导致了一个问题:如果将其中一个字符串放入哈希集合中(例如作为Dictionary的键),除非碰巧,否则您将永远无法再次查找它们。因此,默认的Object.GetHashCode()方法对于哈希目的来说并不是特别有用。

解决方法是根据具体的需求来重写GetHashCode()方法。对于字符串,它已经重写了GetHashCode()方法,以确保相同值的字符串具有相同的哈希码。但对于其他类型的对象,如果引用是您想要跟踪的值,那么默认的GetHashCode()实现是可以接受的。

如果需要使用哈希码来唯一标识对象或比较对象的值是否相同,应该重写Object.GetHashCode()方法,并根据具体需求实现自定义的哈希码生成算法。

0
0 Comments

默认的Object.GetHashCode()方法的实现是通过返回对象的内存地址来生成哈希码。然而,这种实现并不总是符合我们的需求,因为在大多数情况下,我们更希望通过对象的内容来生成哈希码,以确保在比较对象时能够正确地判断它们是否相等。

为了解决这个问题,我们可以重写Object.GetHashCode()方法,并根据对象的内容来生成哈希码。重写时,还需要确保Equals()方法的实现与GetHashCode()方法保持一致,即如果两个对象通过Equals()方法比较返回true,则它们的哈希码必须相同。通常还会提供==和!=运算符的实现,以及实现IEquatable接口。

在现代的C#版本中,可以使用HashCode类型来方便地生成哈希码。例如,可以使用HashCode.Combine()方法来生成哈希码,该方法可以接受多个字段作为参数,并生成它们的组合哈希码。

当HashCode类型不可用时,通常使用一种称为"factored sum"的算法来生成哈希码。这种算法可以避免在对配对的值进行哈希计算时发生碰撞。例如,对于一个基本的两个字段的哈希码生成算法,可以使用如下代码:

unchecked
{
    int hash = 27;
    hash = (13 * hash) + field1.GetHashCode();
    hash = (13 * hash) + field2.GetHashCode();
    return hash;
}

这种算法的优点是:

- {1,2}的哈希码与{2,1}的哈希码不同

- {1,1}的哈希码与{2,2}的哈希码不同

这在使用非加权求和或异或(^)等简单算法时可能是常见的情况。

关于选择27和13的原因,实际上是任意选择的。现在,我们更倾向于使用HashCode.Combine()方法来生成哈希码,因此这种选择已经不再重要。

总结起来,重写Object.GetHashCode()方法的原因是为了根据对象的内容生成哈希码,以便正确比较对象的相等性。解决方法是使用HashCode类型来方便地生成哈希码,或者使用factored sum算法手动生成哈希码。

0