Object.GetHashCode()对于引用和值类型来说是唯一的吗?

26

MSDN文档中关于Object.GetHashCode()的描述有三条相互矛盾的规则。

  1. 如果两个相同类型的对象表示相同的值,则哈希函数必须为任一对象返回相同的常量值。
  2. 为了获得最佳性能,哈希函数必须为所有输入生成随机分布。
  3. 无论对对象进行了哪些更改,哈希函数都必须返回完全相同的值。

规则1和3对我来说是相互矛盾的。

Object.GetHashCode()返回基于对象的还是引用的唯一数字。如果我重写该方法,我可以选择使用什么,但如果有人知道内部使用了什么,我想知道。

6个回答

30
规则一和规则三在我的理解中是相互矛盾的。
在某种程度上,它们确实是。原因很简单:如果一个对象被存储在哈希表中,并且通过改变其值来改变其哈希值,那么哈希表就已经失去了该值,并且您无法通过查询哈希表找到它。在将对象存储在哈希表中时,保持它们的哈希值非常重要。
为了实现这一点,通常最简单的方法是使可哈希对象不可变,从而避免整个问题。然而,仅使确定哈希值的字段不可变就足以解决问题。
考虑以下示例:
struct Person {
    public readonly string FirstName;
    public readonly string Name;
    public readonly DateTime Birthday;

    public int ShoeSize;
}

人们的生日和姓名很少会改变(除非结婚)。然而,他们的鞋码可能会任意增长,甚至缩小。因此,使用生日和姓名来识别人是合理的,但不应该使用鞋码。哈希值应该反映这一点:

public int GetHashCode() {
    return FirstName.GetHashCode() ^ Name.GetHashCode() ^ Birthday.GetHashCode();
}

由于在C#中所有对象都是可哈希的(GetHashCode() 是基本 Object 类型的一部分),您会建议使所有对象都不可变 - 这并不是很实用,对吧? - thewhiteambit
1
@thewhiteambit 不是所有的对象都适合作为哈希表键。仅仅因为它们可以被哈希化并不意味着它们应该被用作键。而且,GetHashCodeObject 基类的一个方法,这只是 C# 语言中一个糟糕的设计决策。此外,我的回答并不是说你必须使每个哈希表键类型都是不可变的——仅仅是这样做会有很大的帮助。 - Konrad Rudolph
你说得对,我只是指出了句子“要实现这个目标,通常最简单的方法是使可哈希对象不可变”——由于所有对象都是可哈希的(出于糟糕的设计选择),这种尝试会使所有可哈希对象(即所有对象)都不可变。但我想你不是那个意思。你可能只是想让所有想要存储在哈希集合中的对象都是不可变的。 - thewhiteambit
1
@thewhiteambit 噢,是的。就像:如果你可以控制要哈希的对象类型,强烈建议将其设置为不可变的。 - Konrad Rudolph
你为什么要使用 ^ 符号? - Farhad Zamani
1
@FarhadZamani Xor是一种简单的混合哈希方法。它不是非常好-理想情况下,您应该使用其他方法,例如HashCode.Combine,但是(如果我没记错的话)当我写这个答案时还没有可用。 - Konrad Rudolph

5

我不确定你指的是哪个MSDN文档。查看Object.GetHashCode的当前文档 (http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx) 提供以下“规则”:

  • 如果两个对象比较相等,则每个对象的GetHashCode方法必须返回相同的值。但是,如果两个对象不相等,则两个对象的GetHashCode方法不必返回不同的值。

  • 对象的GetHashCode方法必须在决定对象的Equals方法的返回值的对象状态没有修改的情况下始终返回相同的哈希代码。请注意,这仅对应用程序的当前执行有效,在再次运行应用程序时可以返回不同的哈希代码。

  • 为获得最佳性能,哈希函数必须为所有输入生成随机分布。

如果您提到的是第二个要点,则关键短语是“只要对象状态没有修改”和“仅对应用程序的当前执行有效”。

此外,根据文档,

一个哈希函数用于快速生成与对象值对应的数字(哈希码)。哈希函数通常针对每个类型进行特定设计,并且必须至少使用一个实例字段作为输入。关于实际实现,它明确说明派生类只有在定义值相等性为引用相等性且类型不是值类型时才能推迟到Object.GetHashCode实现。换句话说,由于没有真正的实例字段可用,因此Object.GetHashCode的默认实现将基于引用相等性,并且不能保证不同对象返回唯一的返回值。否则,您的实现应该特定于您的类型,并且应该使用至少一个实例字段。例如,String.GetHashCode的实现返回相同的哈希码以表示相同的字符串值,因此如果两个String对象表示相同的字符串值,则它们将返回相同的哈希码,并使用字符串中的所有字符生成该哈希值。

那是我读过的最啰嗦和混乱的答案。它让我比起一开始更加困惑了。 - bleepzter

4

规则1和3并不真正矛盾。

对于引用类型,哈希码是从对象的引用派生出来的 - 更改对象的属性,引用仍然相同。

对于值类型,哈希码是从值派生出来的,更改值类型的属性会得到完全新的值类型实例。


1
在处理GetHashCode(超出Microsoft规则)的方法方面,Eric Lipperts(C#的联合设计师)的博客文章“Guidelines and rules for GetHashCode”提供了非常好的解释。这里不推荐添加超链接(因为它们可能会失效),但这个链接非常值得一看。如果链接丢失,根据上面提供的信息,也可以找到它。

0

我无法确定在真正的.NET Framework中如何实现Object.GetHashCode,但在Rotor中,它使用SyncBlock索引作为哈希码。有一些关于此的博客文章在网上,但大多数都是来自2005年。


0

默认情况下,它是基于对象的引用来进行操作的,但这意味着它是完全相同的对象,因此两者将返回相同的哈希值。但哈希应该基于值,就像字符串类的情况一样。"a"和"b"将具有不同的哈希值,但"a"和"a"将返回相同的哈希值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接