如何识别GetHashCode的错误实现?

时间:2013-07-08 19:08:31

标签: c# equals gethashcode

我有一个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的实现?

2 个答案:

答案 0 :(得分:2)

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

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

我的理解是在NHibernate的情况下“相等”显然更加棘手,结果你看到了复杂的实现。我认为这主要是因为某些对象属性可能尚未可用 - 在这种情况下比较“身份”对象就足够了。

答案 1 :(得分:1)

令人遗憾的是,虽然有两个不同的问题,即相等测试方法可以有意义地询问任何一对对象引用XY,但只有一个Equals方法和一个{ {1}}方法。

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

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

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

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

看起来代码有时会使用类型ValueType的实例,其中第一个问题更合适,有时候第二个问题更合适。虽然这可能是可行的,但我认为拥有一个公共数据持有者对象可能会更好,如果有必要的话,可以将对象的相等性检查方法适用于一种用途或另一种用途(例如,具有{{1}可以由PlayListPlaylistData包裹的对象。包装类可以有MutablePlaylistImmutablePlaylist方法,这些方法会使包装器无效并在对象周围返回一个新的包装器(使用包装器将确保系统知道给定的InvalidateAndMakeImmutable引用是否可以接触可能会改变它的代码。)