C# 重写GetHashCode()

C# 重写GetHashCode(),c#,hash,resharper,gethashcode,C#,Hash,Resharper,Gethashcode,在中,Jon Skeet提到他通常使用这种算法覆盖GetHashCode() 现在,我已经尝试使用它,但是Resharper告诉我GetHashCode()方法应该只使用只读字段进行散列(尽管它编译得很好)。什么是一个好的实践,因为现在我真的不能让我的字段是只读的 我尝试通过Resharper生成这个方法,结果如下 public override int GetHashCode() { return base.GetHashCode(); } 老实说,这没有多大作用…如果所有字段都是可变的

在中,Jon Skeet提到他通常使用这种算法覆盖GetHashCode()

现在,我已经尝试使用它,但是Resharper告诉我GetHashCode()方法应该只使用只读字段进行散列(尽管它编译得很好)。什么是一个好的实践,因为现在我真的不能让我的字段是只读的

我尝试通过Resharper生成这个方法,结果如下

public override int GetHashCode()
{
  return base.GetHashCode();
}

老实说,这没有多大作用…

如果所有字段都是可变的,并且您必须实现
GetHashCode
方法,恐怕这就是您需要的实现

public override int GetHashCode() 
{ 
    return 1; 
} 
是的,这是低效的,但这至少是正确的

问题在于
GetHashCode
被字典和HashSet集合用来将每个项放入一个bucket中。如果hashcode是基于一些可变字段计算的,并且在将对象放入HashSet或Dictionary后字段确实发生了更改,则无法再从HashSet或Dictionary中找到该对象

请注意,由于所有对象都返回相同的HashCode 1,这基本上意味着所有对象都放在HashSet或Dictionary中的同一个bucket中。因此,HashSet或Dictionary中始终只有一个bucket。当试图查找对象时,它将对唯一bucket中的每个对象执行相等性检查。这就像在链表中搜索一样


有人可能会说,如果我们能够确保在将对象添加到hashcode或Dictionary集合后字段不会更改,那么基于可变字段实现hashcode就可以了。我个人的观点是,这很容易出错。两年后接管您的代码的人可能没有意识到这一点,并意外地破坏了代码。

请注意,您的GetHashCode必须与您的Equals方法齐头并进。若您可以只使用引用相等(当您从来并没有两个不同的类实例可以相等时),那个么您就可以安全地使用从对象继承的Equals和GetHashCode。这比简单地从GetHashCode返回1要好得多。

我个人倾向于在一个没有不可变字段的类中为
GetHashCode()
的每个实现返回不同的数值。这意味着,如果我有一个包含不同实现类型的字典,那么不同类型的不同实例有可能被放入不同的bucket中

比如说

public class A
{
    // TODO Equals override

    public override int GetHashCode()
    {
        return 21313;
    } 
}

public class B
{
    // TODO Equals override

    public override int GetHashCode()
    {
        return 35507;
    } 
}
然后,如果我有一个包含
a
B
和其他类型的实例的
字典
,那么查找的性能将优于
GetHashCode
的所有实现返回相同的数值

还应该注意的是,我使用素数来获得更好的分布


根据我的评论,我提供了一个LINQPad示例,演示了对不同类型使用
return 1
和为每种类型返回不同值之间的性能差异。

可能与以下内容重复:@Ria在这个问题中,没有人谈论只读字段。您可能希望在Eric Lippert的博客上查阅。特别是,请注意“规则:当对象包含在依赖于哈希代码保持稳定的数据结构中时,GetHashCode返回的整数不得更改”一节。他写道,“允许创建一个哈希代码值随对象字段的变化而变化的对象,尽管这很危险。”
strucs
是否可能重复?字段也必须是不可变的吗?我发现存储在
字典中时很难更改它们的字段。在这种情况下,根本不需要重写GetHashCode,默认实现会工作得更好。虽然散列不依赖于对象的内容,但至少不同对象的散列是不同的。@AntonTykhyy如果我们使用默认的GetHashCode(),那么具有相同字段Id=1的两个对象将具有不同的散列代码,因此从不同的bucket进行查找。我假设在本例中,我们希望它们具有相同的Hashcode。@ja72
struct
是相同的。您必须从不可变字段生成哈希代码。@HarveyKwok:这实际上取决于对象是否可相等(相等是基于可变字段的相等)。如果是,那么您是对的,默认的GetHashCode不适用。如果不是,则对象的标识不依赖于其(可变)字段的值,并且默认的GetHashCode是ok。然而,可变的可等式对象不是一个好主意,这与基于可变字段计算GetHashCode不是一个好主意的原因相同。你能扩展最后一句话吗?回想一下这个答案,最后一句话并不适用于这种情况。但是,在使用只读字段实现
GetHashCode()
方法时,使用素数可以确保在字典或hashset的存储桶中实现更好的分布。网上有很多文章,但应该让你开始。对否决票的解释会很好,我觉得这是处理不同类型问题时采取的合理方法。@Lukazoid抱歉,我通常会发表评论-这次忘了@Lukazoid:这个解决方案的问题是它鼓励
O(N^2)
行为。不同类之间缺乏冲突几乎没有帮助,只有当代码混合了类型并破坏了类型系统时才有帮助——也就是说,代码你真的不想帮忙。
public class A
{
    // TODO Equals override

    public override int GetHashCode()
    {
        return 21313;
    } 
}

public class B
{
    // TODO Equals override

    public override int GetHashCode()
    {
        return 35507;
    } 
}