如何识别GetHashCode的错误实现

本文关键字:GetHashCode 错误 实现 识别 何识别 | 更新日期: 2023-09-27 18:21:37

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

我在StackOverflow上读了很多关于GetHashCode的文章。有没有一个例子说明为什么在NHibernate中应该覆盖Equals/GetHashCode?我认为这个帖子可能是最好的信息来源,但它仍然让我感到疑惑。

考虑以下实体及其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);
    }
}

在这个实施方案中吹嘘的安全检查数量似乎太多了。它激发了我的信心——假设写这篇文章的人比我了解更多的角落案例——但也让我想知道为什么我看到这么多简单的实现。

为什么在重写Equals方法时重写GetHashCode很重要?看看所有这些不同的实现。以下是一个简单但评价很高的实现:

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

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

两者同等有效吗?在实现GetHashCode时,是否应该遵循标准的"准则"?上述实施是否存在明显缺陷?如何创建测试用例来验证GetHashCode的实现?

如何识别GetHashCode的错误实现

GetHashCode应该与类/环境的"相等"概念相匹配(除了在容器中保持恒定和快速)。

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

我的理解是,在NHibernate的情况下,"相等"明显更棘手,因此您会看到复杂的实现。我认为这主要是因为一些对象属性可能还不可用——在这种情况下,比较对象的"身份"就足够了。

不幸的是,尽管等式测试方法可以对任何一对对象引用XY提出两个不同的问题,但只有一个Equals方法和一个GetHashCode方法。

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

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

(*)一般而言,不同类型的对象应报告不平等。在某些情况下,如果所有可以访问私有类的代码都只在匹配的公共类型中存储引用,那么从同一个公共类继承的不同私有类的对象应该被视为相等的,但这最多只是一个非常狭窄的例外。

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

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