Подтвердить что ты не робот

Различия между IEquatable <T>, IEqualityComparer <T> и переопределением .Equals() при использовании LINQ в коллекции пользовательских объектов?

У меня возникают трудности с использованием метода Linq.Except() при сравнении двух коллекций настраиваемого объекта.

Я вывел свой класс из Object и реализовал переопределения для Equals(), GetHashCode() и операторов == и !=. Я также создал метод CompareTo().

В моих двух коллекциях, в качестве отладочного эксперимента, я взял первый элемент из каждого списка (который является дубликатом) и сравнил их следующим образом:

itemListA[0].Equals(itemListB[0]);     // true
itemListA[0] == itemListB[0];          // true
itemListA[0].CompareTo(itemListB[0]);  // 0

Во всех трех случаях результат такой, какой я хотел. Однако, когда я использую метод Linq Except(), дублирующие элементы не удаляются:

List<myObject> newList = itemListA.Except(itemListB).ToList();

Узнав о том, как Linq выполняет сравнения, я обнаружил различные (конфликтующие?) методы, которые говорят, что мне нужно наследовать от IEquatable<T> или IEqualityComparer<T> и т.д.

Я запутался, потому что, когда я наследую, например, IEquatable<T>, я должен предоставить новый метод Equals() другой подписью из того, что я уже переопределил. Должен ли я иметь два таких метода с разными сигнатурами, или мне больше не нужно выводить мой класс из Object?

Мое определение объекта (упрощенное) выглядит следующим образом:

public class MyObject : Object
{
    public string Name {get; set;}
    public DateTime LastUpdate {get; set;}

    public int CompareTo(MyObject other)
    {
        // ...
    }

    public override bool Equals(object obj)
    {
        // allows some tolerance on LastUpdate
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;
            hash = hash * 23 + Name.GetHashCode();
            hash = hash * 23 + LastUpdate.GetHashCode();
            return hash;
        }
    }

    // Overrides for operators
}

Я заметил, что когда я наследую от IEquatable<T>, я могу сделать это с помощью IEquatable<MyObject> или IEquatable<object>; требования к изменению подписи Equals() при использовании одного или другого. Каков рекомендуемый способ?

То, что я пытаюсь выполнить:

Я хочу иметь возможность использовать Linq (Distinct/Except), а также стандартные операторы равенства (== и !=) без дублирования кода. Сравнение должно позволить двум объектам считаться равными, если их имя идентично, а свойство LastUpdate находится в пределах нескольких секунд (задано пользователем).

Edit:

Отображение кода GetHashCode().

4b9b3361

Ответ 1

Неважно, переопределяете ли вы object.Equals и object.GetHashCode, реализуете IEquatable или предоставляете IEqualityComparer. Все они могут работать несколько иначе.

1) Переопределение Equals и GetHashCode от object:

Это базовый случай, в некотором смысле. Как правило, это работает, предполагая, что вы в состоянии отредактировать тип, чтобы гарантировать, что реализация этих двух методов будет по желанию. Нет ничего плохого в том, чтобы делать это во многих случаях.

2) Реализация IEquatable

Ключевым моментом здесь является то, что вы можете (и должны) реализовать IEquatable<YourTypeHere>. Ключевое различие между этим и # 1 состоит в том, что у вас есть сильная типизация для метода Equals, а не просто использование object. Это лучше для удобства программиста (добавленный тип безопасности), а также означает, что любые типы значений не будут помещаться в бокс, поэтому это может повысить производительность для пользовательских структур. Если вы сделаете это, вы должны в значительной степени сделать это в дополнение к # 1, а не вместо. Наличие метода Equals здесь отличается функциональностью от object.Equals будет... плохой. Не делайте этого.

3) Реализация IEqualityComparer

Это полностью отличается от первых двух. Идея здесь в том, что объект не получает собственный хэш-код или видит, что он равен чему-то другому. Точка этого подхода состоит в том, что объект не знает, как правильно получить хэш или посмотреть, совпадает ли он с чем-то другим. Возможно, это потому, что вы не контролируете код типа (т.е. Стороннюю библиотеку), и они не удосужились переопределить поведение или, возможно, переопределили его, но вам просто нужно ваше собственное уникальное определение "равенства" в этот конкретный контекст.

В этом случае вы создаете полностью отдельный "сравнительный" объект, который принимает два разных объекта и информирует вас о том, являются они равными или нет или что представляет собой хэш-код одного объекта. При использовании этого решения не имеет значения, что делают методы Equals или GetHashCode в самом типе, вы не будете использовать его.


Обратите внимание, что все это полностью не связано с оператором ==, который является его собственным зверьем.

Ответ 2

Основной шаблон, который я использую для равенства в объекте, следующий. Обратите внимание, что только 2 метода имеют реальную логику, специфичную для объекта. Остальное - это только код плиты котла, который питается этими двумя способами.

class MyObject : IEquatable<MyObject> { 
  public bool Equals(MyObject other) { 
    if (Object.ReferenceEquals(other, null)) {
      return false;
    }

    // Actual equality logic here
  }

  public override int GetHashCode() { 
    // Actual Hashcode logic here
  }

  public override bool Equals(Object obj) {
    return Equals(obj as MyObject);
  }

  public static bool operator==(MyObject left, MyObject right) { 
    if (Object.ReferenceEquals(left, null)) {
      return Object.ReferenceEquals(right, null);
    }
    return left.Equals(right);
  }

  public static bool operator!=(MyObject left, MyObject right) {
    return !(left == right);
  }
}

Если вы следуете этому шаблону, нет необходимости предоставлять пользовательский IEqualityComparer<MyObject>. EqualityComparer<MyObject>.Default будет достаточно, поскольку он будет полагаться на IEquatable<MyObject> для выполнения проверок равенства

Ответ 3

Вы не можете "разрешить некоторый допуск на LastUpdate", а затем использовать реализацию GetHashCode(), которая использует строгое значение LastUpdate!

Предположим, что экземпляр this имеет LastUpdate в 23:13:13.933, а экземпляр obj имеет 23:13:13.932. Тогда эти два могут сравниться с вашей идеей толерантности. Но если это так, их хэш-коды должны быть одинаковыми. Но это не произойдет, если вам не очень повезло, поскольку DateTime.GetHashCode() не должен давать один и тот же хеш для этих двух раз.

Кроме того, ваш метод Equals может быть математически переходным. И "приблизительно равный" нельзя сделать переходным. Его транзитивное замыкание - это тривиальное отношение, которое идентифицирует все.