2011-11-15 4 views
3

У меня есть класс сущностей, как это (с большим количеством вещи пропали без вести):Как Contains возвращает false, но GetHashCode() возвращает одинаковое число, а Equals возвращает true?

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 Cookbook. Вот мой базовый класс:

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.

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

+0

Из любопытства, не могли бы вы проверить его с 'частных Iesi.Collections.Generic.HashedSet детей = новый Iesi.Collections.Generic.HashedSet ();' –

ответ

3

Для того, чтобы это сработало, мне пришлось изменить метод класса «GetHashCode», чтобы lazy-оценить хэш-код, но как только вычисленный, кешируйте результат и никогда не позволяйте ему меняться. Вот моя новая реализация GetHashCode:

private int? requestedHashCode; 

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

Для лучшей реализации класса базового объекта, см AbstractEntity.

3

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

+0

Глядя в него с ReSharper, это просто обертка вокруг standard 'Dictionary ' и 'Contains' вызывает' ContainsKey'. –

+0

см. Это [сообщение] (http://stackoverflow.com/questions/2197678/dictionary-containskey-not-working-as-expected) о Dictionary.ContainsKey – sq33G

2

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

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

+0

Хорошие баллы. Оба они получают из общего базового класса Entity (рекомендуется из всей литературы NHibernate). Я разместил базовый класс выше. –

3

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