Search code examples
c#nhibernateequalscontainsgethashcode

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.


Solution

  • 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.