Contains如何返回false,但GetHashCode()返回相同的数字,Equals返回true?

时间:2011-11-15 20:02:43

标签: c# nhibernate equals contains gethashcode

我有一个像这样的实体类(缺少很多东西):

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第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)
  4. ......如下所述,这正在改变GetHashCode()

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

    不过,我认为还存在一些根本性错误。

4 个答案:

答案 0 :(得分:3)

我无法想到任何其他方式可能会发生这种情况 - Iesi.Collections.Generic.HashedSet必须包含自己的Contains,其行为与我们预期的不同。

答案 1 :(得分:3)

Child是否覆盖object.Equals(object)并实施IEquatable<Child>?集合正在执行的相等可能与您在代码示例的第二行上调用的Equals方法不同。

答案 2 :(得分:3)

为了实现这一点,我不得不将我的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

答案 3 :(得分:2)

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

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