简体   繁体   中英

How can Contains returns false but GetHashCode() returns the same number, and Equals returns true?

I have an entity class like this (with lots of stuff missing):

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);
        }
    }
}

However, when I attempt to remove a child, the if statement evaluates to false . So, I put a breakpoint on the if statement, and evaluated certain expressions:

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

My understanding is that if GetHashCode returns identical values, it then checks Equals . Why is Contains returning false?


Both of my Parent and Child entities inherit from a common Entity base class, which is a non-generic version of the generic entity base class from page 25 of the NHibernate 3.0 Cookbook. Here is my base class:

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();
    }

}

After further investigation, I feel something like this is happening:

  1. Calling parent.AddChild(child)
  2. Saving to the database, which caused child.Id to be generated
  3. Calling parent.RemoveChild(child)

...and as discussed below, this was changing GetHashCode() .

This was the result of a bug in my program - I was supposed to reload parent between steps 2 and 3.

Still, I think there's something more fundamentally wrong.

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

Does Child both override object.Equals(object) and implement IEquatable<Child> ? It's possible that the equality that the collection is doing isn't the same as the Equals method you're calling on the 2nd line of your code sample.

To get this to work, I had to change my Entity class' GetHashCode method to lazy-evaluate the hash code, but once computed, cache the result and never let it change. Here is my new implementation of GetHashCode :

    private int? requestedHashCode;

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

For a better implementation of a base entity class, see AbstractEntity .

This could happen if Equals(Child) is implemented differently to the override of Equals(object) . It would really depend on what Child looked like.

It can also happen due to the effect that Henk mention - is Parent part of the calculation of the hash code and equality, for example? If so, setting Parent to null will probably change the hash code of the child to be one other than the hash code which is recorded in the HashSet . It's not a problem if Parent isn't part of the equality/hash calculation, although it's still somewhat odd to put a mutable type into a hash set or use it as the key in a hash table.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM