【问题标题】:How can I identify a bad implementation of GetHashCode?如何识别 GetHashCode 的错误实现?
【发布时间】:2013-07-06 05:28:47
【问题描述】:

我有一个 GetHashCode 的实现,我认为它相当健壮,但老实说,我是从互联网深处挖掘出来的,虽然我理解所写的内容,但我觉得没有资格将其描述为GetHashCode 的“好”或“坏”实现。

我在 StackOverflow 上阅读了大量有关 GetHashCode 的内容。 Is there a sample why Equals/GetHashCode should be overwritten in NHibernate? 我认为这个帖子可能是最好的信息来源,但它仍然让我感到疑惑。

考虑以下实体及其给定的 Equals 和 GetHashCode 实现:

public class Playlist : IAbstractDomainEntity
{
    public Guid Id { get; set; }
    public string Title { get; set; 
    public Stream Stream { get; set; }
    //  Use interfaces so NHibernate can inject with its own collection implementation.
    public IList<PlaylistItem> Items { get; set; }
    public PlaylistItem FirstItem { get; set; }
    public Playlist NextPlaylist { get; set; }
    public Playlist PreviousPlaylist { get; set; }

    private int? _oldHashCode;
    public override int GetHashCode()
    {
        // Once we have a hash code we'll never change it
        if (_oldHashCode.HasValue)
            return _oldHashCode.Value;

        bool thisIsTransient = Equals(Id, Guid.Empty);

        // When this instance is transient, we use the base GetHashCode()
        // and remember it, so an instance can NEVER change its hash code.
        if (thisIsTransient)
        {
            _oldHashCode = base.GetHashCode();
            return _oldHashCode.Value;
        }
        return Id.GetHashCode();
    }

    public override bool Equals(object obj)
    {
        Playlist other = obj as Playlist;
        if (other == null)
            return false;

        // handle the case of comparing two NEW objects
        bool otherIsTransient = Equals(other.Id, Guid.Empty);
        bool thisIsTransient = Equals(Id, Guid.Empty);
        if (otherIsTransient && thisIsTransient)
            return ReferenceEquals(other, this);

        return other.Id.Equals(Id);
    }
}

在这个实现中吹捧的安全检查数量似乎超过了顶部。它激发了我的信心——假设写这篇文章的人比我理解更多的极端案例——但也让我想知道为什么我看到这么多简单的实现。

Why is it important to override GetHashCode when Equals method is overridden? 看看所有这些不同的实现。下面是一个简单但评价很高的实现:

  public override int GetHashCode()
  {
        return string.Format("{0}_{1}_{2}", prop1, prop2, prop3).GetHashCode();
  }

这个实现会比我提供的更好还是更差?为什么?

两者是否同样有效?实施 GetHashCode 时是否应遵循标准“指南”?上面的实现有什么明显的缺陷吗?如何创建测试用例来验证 GetHashCode 的实现?

【问题讨论】:

  • 我总是在这里接受的答案中使用 Jon Skeet 的建议:stackoverflow.com/questions/263400/…
  • 您已经遇到了麻烦,因为您有效地使用了一个可变值进行散列。如果项目在存储在任何使用散列的容器中时被修改,这很糟糕。你最终会得到一个损坏的容器。
  • 您的 Id 属性是可变的。我可以将它存储在一个哈希集中,更改它的 ID,然后毫无怨言地存储一个“重复”条目。这违反了设计准则。
  • Jon 提倡的实现不是基于一定数量的属性;这只是一个例子。请记住,GetHashCode() 用于允许将类的实例用作集合中的键,例如字典或哈希表。因此,一个健壮的实现将考虑那些因实例而异的字段,这些字段通常是一个类中的所有字段。在他的示例中,这是 3 个字段;在你的,它看起来像 1。
  • @StriplingWarrior 你知道.. 我真的不确定。我想它可能会在 Session 中存储两个副本,直到调用 Commit 并且如果两者仍然存在,如果违反约束,则会引发异常。

标签: c# equals gethashcode


【解决方案1】:

不幸的是,虽然相等性测试方法可以对任何一对对象引用 XY 有意义地提出两个不同的问题,但只有一个 Equals 方法和一个 GetHashCode 方法。

  • 假设XY 的类型相同(*),那么X 的所有成员是否总是与Y 的相应方法行为相同?在此定义下,对不同数组的两个引用将被报告为不相等,即使它们包含匹配的元素,因为即使它们的元素在某一时刻相同,也可能并不总是如此。

  • 假设 XY 属于同一类型 (*),将同时将所有对对象 X 的引用替换为对对象 Y 的引用,反之亦然,这会影响任何一个的任何成员除了基于身份的GetHashCode 函数?在此定义下,对元素匹配的两个不同数组的引用将被报告为相等。

(*) 一般来说,不同类型的对象应该报告不相等。在某些情况下,如果可以访问私有类的所有代码仅将引用存储在匹配的公共类型中,那么从同一个公共类继承的不同私有类的对象应该被认为是相等的,但在某些情况下可能会争辩说。这至多是一个非常狭窄的例外。

有些情况需要问第一个问题,有些情况需要问第二个问题; EqualsGetHashCode 的默认 Object 实现回答第一个,而默认 ValueType 实现回答第二个。不幸的是,对于给定引用选择哪种比较方法是一个函数,它取决于 reference 的使用方式,而不是被引用实例类型的函数。如果两个对象持有对集合的引用,它们既不会变异也不会暴露给可能这样做的代码,出于封装其内容的目的,持有这些引用的对象的相等性应该取决于集合的内容,而不是它们的身份.

看起来代码有时会以第一个问题更合适的方式使用PlayList 类型的实例,有时以第二个问题更合适的方式使用。虽然这可能是可行的,但我认为最好有一个通用的数据持有者对象,如果需要,它可以被一个对象包装,该对象的相等检查方法适合一种或另一种用途(例如,有一个PlaylistData可以被MutablePlaylistImmutablePlaylist 包裹的对象)。包装器类可能有 InvalidateAndMakeImmutableInvalidateAndMakeMutable 方法,这些方法会使包装器无效并返回围绕对象的新包装器(使用包装器将确保系统知道给定的 Playlist 引用是否可能暴露给可能的代码变异它)。

【讨论】:

    【解决方案2】:

    GetHashCode 应该与您的类/环境的“相等”概念相匹配(除了在容器中保持不变且快速)。

    在正常情况下,“相等”是比较对应对象的所有字段(值类型比较)。在这种情况下,以某种方式合并所有字段的哈希码的简单实现就足够了。

    我的理解是,在 NHibernate 的情况下,“相等”要复杂得多,因此您会看到复杂的实现。我相信这主要是由于某些对象属性可能尚不可用 - 在这种情况下比较“身份”对象就足够了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-23
      • 2016-07-22
      • 2017-02-08
      • 2014-11-29
      • 2023-03-24
      • 1970-01-01
      • 2023-03-25
      • 2011-02-22
      相关资源
      最近更新 更多