【问题标题】:Overriding GetHashCode()覆盖 GetHashCode()
【发布时间】:2012-07-13 22:39:15
【问题描述】:

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

public override int GetHashCode()
{
  unchecked // Overflow is fine, just wrap
  {
    int hash = 17;
    // Suitable nullity checks etc, of course :)
    hash = hash * 23 + Id.GetHashCode();
    return hash;
  }
}

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

我尝试通过 Resharper 生成此方法,结果如下。

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

说实话,这并没有多大贡献......

【问题讨论】:

标签: c# hash resharper gethashcode


【解决方案1】:

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

public override int GetHashCode() 
{ 
    return 1; 
} 

是的,这是低效的,但至少是正确的。

问题在于 Dictionary 和 HashSet 集合使用GetHashCode 将每个项目放入存储桶中。如果hashcode是根据一些可变字段计算出来的,而对象放入HashSet或Dictionary后字段确实发生了变化,则无法再从HashSet或Dictionary中找到该对象。

请注意,所有对象都返回相同的 HashCode 1,这基本上意味着所有对象都被放入 HashSet 或 Dictionary 的同一个桶中。因此,HashSet 或 Dictionary 中始终只有一个桶。在尝试查找对象时,它将对唯一存储桶内的每个对象进行相等性检查。这就像在链表中进行搜索。

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

【讨论】:

  • strucs 怎么样?字段也必须是不可变的吗?我发现在Dictionary 中存储时很难更改他们的字段。
  • 在这种情况下根本不需要重写 GetHashCode,默认实现会更好。虽然哈希不会依赖于对象的内容,但至少对于不同的对象会有所不同。
  • @AntonTykhyy 如果我们使用默认的 GetHashCode(),具有相同字段 Id=1 的两个对象将具有不同的 HashCode,因此从不同的存储桶中查找。我假设在这种情况下,我们希望它们具有相同的哈希码。
  • @ja72 struct 是一样的。您必须从不可变字段生成哈希码。
  • @HarveyKwok:嗯,这实际上取决于对象是否相等(相等基于可变字段的相等)。如果是,那么你是对的,默认的 GetHashCode 是不合适的。如果不是,则对象的身份不依赖于其(可变)字段的值,并且默认 GetHashCode 是可以的。然而,可变的等价对象不是一个好主意,原因与基于可变字段计算 GetHashCode 不是一个好主意的原因相同。
【解决方案2】:

请注意,您的 GetHashCode 必须与您的 Equals 方法齐头并进。而且,如果您可以只使用引用相等(当您的类的两个不同实例可以相等时),那么您可以安全地使用从 Object 继承的 Equals 和 GetHashCode。这比 GetHashCode 中的 return 1 效果要好得多。

【讨论】:

    【解决方案3】:

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

    例如

    public class A
    {
        // TODO Equals override
    
        public override int GetHashCode()
        {
            return 21313;
        } 
    }
    
    public class B
    {
        // TODO Equals override
    
        public override int GetHashCode()
        {
            return 35507;
        } 
    }
    

    如果我有一个Dictionary<object, TValue> 包含AB 和其他类型的实例,则查找的性能将比GetHashCode 的所有实现返回相同的数值要好。

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

    根据 cmets,我提供了一个 LINQPad 示例 here,它演示了将 return 1 用于不同类型和为每种类型返回不同值之间的性能差异。

    【讨论】:

    • 你能扩展你的最后一句话吗?
    • 回想一下这个答案,最后一句话并不真正适用于这种情况。但是,在使用只读字段实现GetHashCode() 方法时,使用素数可确保您更有可能在字典或哈希集的存储桶上实现更好的分布。网上有很多文章,但this SO question 应该可以帮助您入门。
    • 对downvote的解释会很好,我觉得这是处理不同类型时采取的合理方法。
    • @Lukazoid 抱歉,我通常会发表评论 - 这次忘了!
    • @Lukazoid:这个解决方案的问题是它鼓励O(N^2) 行为。不同类之间没有冲突几乎没有帮助,只有当代码混合类型并破坏类型系统时才会有帮助 - 即你真的不想打扰帮助的代码。
    猜你喜欢
    • 2011-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-11
    相关资源
    最近更新 更多