为什么Contains返回false,而GetHashCode()返回相同的数字,而Equals返回true ?

本文关键字:返回 数字 Equals true false Contains GetHashCode 为什么 | 更新日期: 2023-09-27 18:14:37

我有一个这样的实体类(有很多东西丢失):

class Parent
{
    private readonly Iesi.Collections.Generic.ISet<Child> children =
        new Iesi.Collections.Generic.HashedSet<Child>();
    public virtual void AddChild(Child child)
    {
        if (!this.children.Contains(child))
        {
            this.children.Add(child);
            child.Parent = this;
        }
    }
    public virtual void RemoveChild(Child child)
    {
        if (this.children.Contains(child))
        {
            child.Parent = null;
            this.children.Remove(child);
        }
    }
}

然而,当我试图删除子节点时,if语句的计算结果为false。因此,我在if语句上放置了一个断点,并计算了某些表达式:

this.children.Contains(child) => false
this.children.ToList()[0].Equals(child) => true
this.children.ToList()[0].GetHashCode() => 1095838920
child.GetHashCode() => 1095838920

我的理解是,如果GetHashCode返回相同的值,它然后检查Equals。为什么Contains返回false?


我的ParentChild实体都继承自一个通用的Entity基类,这是NHibernate 3.0 Cookbook第25页通用实体基类的非泛型版本。这是我的基类:

public class Entity : IEntity
{
    public virtual Guid Id { get; private set; }
    public override bool Equals(object obj)
    {
        return Equals(obj as Entity);
    }
    private static bool isTransient(Entity obj)
    {
        return obj != null &&
            Equals(obj.Id, Guid.Empty);
    }
    private Type getUnproxiedType()
    {
        return GetType();
    }
    public virtual bool Equals(Entity other)
    {
        if (other == null)
            return false;
        if (ReferenceEquals(this, other))
            return true;
        if (!isTransient(this) &&
            !isTransient(other) &&
            Equals(Id, other.Id))
        {
            var otherType = other.getUnproxiedType();
            var thisType = getUnproxiedType();
            return thisType.IsAssignableFrom(otherType) ||
                otherType.IsAssignableFrom(thisType);
        }
        return false;
    }
    public override int GetHashCode()
    {
        if (Equals(Id, Guid.Empty))
            return base.GetHashCode();
        return Id.GetHashCode();
    }
}

经过进一步的调查,我觉得发生了这样的事情:

  1. 呼叫parent.AddChild(child)
  2. 保存到数据库,导致child.Id生成
  3. 呼叫parent.RemoveChild(child)

…正如下面所讨论的,这正在改变GetHashCode()

这是我的程序中的一个错误的结果-我应该在步骤2和3之间重新加载parent

为什么Contains返回false,而GetHashCode()返回相同的数字,而Equals返回true ?

我想不出这可能发生的任何其他方式- Iesi.Collections.Generic.HashedSet必须包含自己的Contains,其行为与我们期望的不同。

Child是否都覆盖object.Equals(object)并实现IEquatable<Child> ?这是可能的,相等的集合正在做的是不一样的Equals方法你调用的第二行代码示例。

要使其工作,我必须更改Entity类的GetHashCode方法来延迟计算哈希码,但是一旦计算完成,就缓存结果并且不让它更改。这是我对GetHashCode的新实现:

    private int? requestedHashCode;
    public override int GetHashCode()
    {
        if (!requestedHashCode.HasValue)
        {
            requestedHashCode = isTransient(this) 
                ? base.GetHashCode() 
                : this.Id.GetHashCode();
        }
        return requestedHashCode.Value;
    }

有关基实体类的更好实现,请参见AbstractEntity。

如果Equals(Child)的实现与Equals(object)的覆盖不同,则可能发生这种情况。这真的取决于Child的样子。

它可以也可以发生由于Henk提到的影响- Parent是计算哈希码和等式的一部分,例如?如果是这样,将Parent设置为null可能会改变子节点的哈希码,而不是记录在HashSet中的哈希码。如果Parent 不是相等/哈希计算的一部分,这不是问题,尽管将可变类型放入哈希集或将其用作哈希表中的键仍然有些奇怪。