【问题标题】:How can Contains returns false but GetHashCode() returns the same number, and Equals returns true?怎么可能 Contains 返回 false 但 GetHashCode() 返回相同的数字,而 Equals 返回 true?
【发布时间】:2011-12-29 20:32:20
【问题描述】:

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

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

不过,我认为还有更根本的错误。

【问题讨论】:

  • 出于好奇,你能用private Iesi.Collections.Generic.HashedSet&lt;Child&gt; children = new Iesi.Collections.Generic.HashedSet&lt;Child&gt;();测试一下吗

标签: c# nhibernate equals contains gethashcode


【解决方案1】:

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

【讨论】:

  • 用 ReSharper 研究它,它只是标准 Dictionary&lt;T,object&gt;Contains 调用 ContainsKey 的包装器。
  • 看到这个post关于Dictionary.ContainsKey
【解决方案2】:

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

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

【讨论】:

  • 好点。两者都派生自一个共同的Entity 基类(从所有 NHibernate 文献中推荐)。我已经发布了上面的基类。
【解决方案3】:

Child 是否同时覆盖 object.Equals(object) 并实现 IEquatable&lt;Child&gt;?集合所做的相等性可能与您在代码示例的第二行调用的 Equals 方法不同。

【讨论】:

    【解决方案4】:

    为了让它工作,我不得不改变我的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

    【讨论】:

      猜你喜欢
      • 2020-07-18
      • 2014-10-14
      • 1970-01-01
      • 1970-01-01
      • 2015-06-21
      • 2012-06-29
      • 2013-02-26
      • 1970-01-01
      • 2014-07-22
      相关资源
      最近更新 更多