Реализация IEquatables вызывается только в том случае, если переопределено значение base Equals

#c# #.net #generics

#c# #.net #общие

Вопрос:

У меня есть следующий класс

     class Product : IEquatable<Product>
{
    public Guid Id { get; set; }
    public bool Equals(Product other)
    {
        return Id.Equals(other.Id);
    }
}
  

Если я попытаюсь создать уникальный список элементов списка следующим образом

             Guid a = Guid.NewGuid();
        List<Product> listA = new List<Product>();
        listA.Add(new Product(){Id = a});

        List<Product> listB = new List<Product>();
        listB.Add(new Product()
        {
            Id = a
        });
        Debug.Assert(listA.Union(listB).Count()==1);
  

возвращаются два элемента, это происходит до тех пор, пока я не переопределю метод object.Equals, как только я это сделаю, и мой код выглядит следующим образом

 class Product : IEquatable<Product>
{
    public Guid Id { get; set; }
    public bool Equals(Product other)
    {
        if (ReferenceEquals(null, other)) return false;
        if (ReferenceEquals(this, other)) return true;
        return other.Id.Equals(Id);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != typeof (Product)) return false;
        return Equals((Product) obj);
    }

    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }
}
  

теперь вызывается мой метод IEquatable Equals, но только если я переопределяю базовый метод, более того, если я ставлю точку останова для метода object equals, он никогда не вызывается.

Почему это так?

—-ОБНОВИТЬ

Так и с классом продукта

     class Product : IEquatable<Product>
{
    public Guid Id
    {
        get;
        set;
    }
    public bool Equals(Product other)
    {
        return Id.Equals(other.Id);
    }

    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }

}
  

Если GetHashCode удален, IEquatable реализация Equals никогда не выполняется Я понимаю, что вы обычно должны реализовывать Equals и GetHashCode вместе, не так ли?

Комментарии:

1. GetHashCode Похоже, добавление этой реализации в ваш исходный класс дает вам желаемое поведение.

Ответ №1:

Проблема в исходной реализации, в которой вы не переопределяете GetHashcode метод. Under the hood Union использует Set<T> структуру стилей для удаления дубликатов. Эта структура помещает объекты в сегменты на основе значения, возвращаемого из GetHashCode . Если хэш-код не совпадает между равными объектами (что он должен делать), то их потенциально можно поместить в разные сегменты и никогда не сравнивать с Equals

В общем, если вы реализуете IEquatable<T> , вы всегда должны

  • Переопределение Object.Equals
  • Переопределение Object.GetHashCode

Невыполнение обоих приведет вас к подобным ситуациям.

Обратите внимание, что ваша реализация может быть немного упрощена

 class Product : IEquatable<Product>
{
  public Guid Id { get; set; }
  public bool Equals(Product other) 
  {
    if (ReferenceEquals(null, other)) {
      return false;
    }
    return other.Id == this.Id;
  }

  public override bool Equals(object obj) 
  {
    return Equals(obj as Product);
  }    

  public override int GetHashCode()
  {
    return Id.GetHashCode();
  }
}
  

Комментарии:

1. Это просто реализация метода equals в resharper по умолчанию. Если это так (и я уверен, что вы правы, кстати), почему точка останова не попадает? Это ошибка пользователя? Также здесь BOL msdn.microsoft.com/en-us/library/ms224763.aspx предполагает, что он должен искать IEquatable?

2. Я использую стандартный метод объединения LINQ, приведенный выше код взят из тестового жгута, чтобы подтвердить то, что я вижу

3. @KevHunter я запустил этот конкретный пример в отладчике и подтвердил, что override bool Equals он никогда не вызывается в образце. Оно всегда будет проходить через IEquatable<T> версию. Вы запускали это локально?

4. Добавление метода GetHashCode, предложенного Aakash, дает мне ожидаемое поведение, есть идеи, почему это?

5. @KevHunter о, я не понял, что в вашем исходном impl вы не переопределяли GetHashcode . Да, это определенно предварительное требование. Equals Метод даже не будет вызван, если хэш-коды не совпадают.

Ответ №2:

Документация для Enumerable.Объединение говорит:

Средство сравнения равенства по умолчанию, Default, используется для сравнения значений типов, которые реализуют универсальный интерфейс IEqualityComparer(Of T). Чтобы сравнить пользовательский тип данных, вам необходимо реализовать этот интерфейс и предоставить свои собственные методы GetHashCode и Equals для этого типа.

Вы реализуете IEquatable . Вам необходимо реализовать IEqualityComparer .