Как может Contains возвращает false, но GetHashCode () возвращает то же число, а Equals возвращает true? - PullRequest
3 голосов
/ 16 ноября 2011

У меня есть такой класс сущностей (чего не хватает):

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?


Обе мои сущности Parent и Child наследуются от общего базового класса Entity, который является неуниверсальной версией базового класса универсальной сущности со страницы 25 поваренной книги NHibernate 3.0. Вот мой базовый класс:

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().

Это было результатом ошибки в моей программе - я должен был перезагрузить parent между шагами 2 и 3.

Тем не менее, я думаю, что есть что-то более фундаментальное, неправильное.

Ответы [ 4 ]

3 голосов
/ 16 ноября 2011

Чтобы заставить это работать, мне пришлось изменить метод Entity class '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 голосов
/ 16 ноября 2011

Child одновременно перекрывает object.Equals(object) и реализует IEquatable<Child>?Возможно, равенство, которое выполняет коллекция, не совпадает с методом Equals, который вы вызываете во 2-й строке примера кода.

3 голосов
/ 16 ноября 2011

Я не могу представить какой-либо другой способ, которым это может произойти - Iesi.Collections.Generic.HashedSet должен содержать свой собственный Contains, который ведет себя не так, как мы ожидаем.

2 голосов
/ 16 ноября 2011

Это может произойти, если Equals(Child) реализован иначе, чем переопределение Equals(object).Это действительно будет зависеть от того, как выглядит Child.

Это может также произойти из-за эффекта, упомянутого Хенком - это Parent часть вычисления хэш-кода и равенства, например?Если это так, установка Parent в null, вероятно, изменит хеш-код дочернего элемента на другой, отличный от хеш-кода, записанного в HashSet.Это не проблема, если Parent не является частью вычисления равенства / хеша, хотя все еще несколько странно помещать изменяемый тип в хеш-набор или использовать его в качестве ключа в хеш-таблице.

...