C# Object.GetHashCode()对于引用或值是唯一的吗?

C# Object.GetHashCode()对于引用或值是唯一的吗?,c#,.net,C#,.net,MSDN文档中描述了3条相互矛盾的规则,说明了该方法的工作原理 如果相同类型的两个对象表示相同的值,则哈希函数必须为任一对象返回相同的常量值 为了获得最佳性能,哈希函数必须为所有输入生成随机分布 哈希函数必须返回完全相同的值,而不考虑对对象所做的任何更改 规则1和3对我来说是矛盾的 Object.GetHashCode()是否基于对象的值或对象的引用返回唯一的数字。如果我重写该方法,我可以选择使用什么,但如果有人知道,我想知道内部使用的是什么。默认情况下,它是基于对对象的引用来执行的,但这意味着

MSDN文档中描述了3条相互矛盾的规则,说明了该方法的工作原理

  • 如果相同类型的两个对象表示相同的值,则哈希函数必须为任一对象返回相同的常量值
  • 为了获得最佳性能,哈希函数必须为所有输入生成随机分布
  • 哈希函数必须返回完全相同的值,而不考虑对对象所做的任何更改
  • 规则1和3对我来说是矛盾的


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

    默认情况下,它是基于对对象的引用来执行的,但这意味着它是完全相同的对象,因此两者都将返回相同的哈希。但是散列应该基于值,就像string类的情况一样。“a”和“b”将具有不同的散列,但“a”和“a”将返回相同的散列

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

    在某种程度上,它们是。原因很简单:如果对象存储在哈希表中,并且通过更改其值更改了其哈希,则哈希表已丢失该值,并且无法通过查询哈希表再次找到该值。重要的是,当对象存储在哈希表中时,它们保留其哈希值

    要实现这一点,通常最简单的方法是使哈希对象不可变,从而避免整个问题。但是,仅使确定哈希值的字段不可变就足够了

    考虑以下示例:

    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();
    }
    

    不确定您所指的MSDN文档是什么。查看Object.GetHashCode()上的当前文档可提供以下“规则”:

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

    • 对象的GetHashCode方法必须始终返回相同的哈希代码,只要不修改确定对象的Equals方法返回值的对象状态。请注意,这仅适用于应用程序的当前执行,如果应用程序再次运行,则可以返回不同的哈希代码

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

    如果您指的是第二个要点,这里的关键短语是“只要不修改对象状态”和“仅适用于应用程序的当前执行”

    同样从文件中

    哈希函数用于快速生成一个数字(哈希代码),该数字对应于对象的值。哈希函数通常特定于每种类型,并且必须使用至少一个实例字段作为输入。[所加的重点是我的。]


    至于实际实现,它明确指出派生类可以遵从Object.GetHashCode实现当且仅当该派生类将值相等定义为引用相等且类型不是值类型时。换句话说,Object.GetHashCode的默认实现将基于引用相等,因为没有可使用的实际实例字段,因此不保证不同对象的唯一返回值。否则,您的实现应该特定于您的类型,并且应该使用至少一个实例字段。例如,String.GetHashCode的实现为相同的字符串值返回相同的哈希代码,因此如果两个字符串对象表示相同的字符串值,则返回相同的哈希代码,并使用字符串中的所有字符生成该哈希值。

    规则1和3实际上并不矛盾

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


    对于值类型,哈希代码是从值派生的,更改值类型的属性,您将获得值类型的全新实例。

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

    关于如何处理
    GetHashCode
    (超越微软规则)的一个非常好的解释在Eric Lipperts(C#的联合设计师)的博客中给出,文章是“”。在这里添加超链接不是一个好的做法(因为它们可能会变得无效),但这是值得的,而且如果超链接丢失,上面的信息可能仍然会找到它。

    这是我读过的最冗长、最混乱的答案。这让我比开始时更加困惑。因为所有对象在C#中都是可散列的(GetHashCode()是非常基本的对象类型的一部分),所以您会建议使所有对象都是不可变的-不是很实用吗?@thewhiteambit No。我的建议是,并不是所有对象都适合作为散列表键的候选对象。仅仅因为它们可以被散列并不意味着它们应该被散列。事实上,
    GetHashCode
    对象
    基类的一种方法,这在C语言中是一个糟糕的设计决策。此外,我的回答不是说你必须做每一件事