Ответ
Метод Equals переопределяют, чтобы изменить логику сравнения объектов по умолчанию (сравнение ссылок для ссылочных типов) на сравнение по значению (по содержимому полей). Это необходимо, когда важна семантическая эквивалентность объектов, а не их идентичность в памяти.
Когда это нужно:
- Для типов-значений (struct): По умолчанию
Equalsдляstructиспользует побитовое сравнение (рефлексию), что может быть неэффективно. Переопределение позволяет задать явную и быструю логику. - Для ссылочных типов (class), которые должны вести себя как значения: Например, классы
Money,Point,Person(в контексте сравнения по ID и имени). - Для корректной работы с коллекциями: Методы
Contains,IndexOfвList<T>, а такжеDictionary<TKey, TValue>иHashSet<T>полагаются наEqualsдля проверки наличия элементов.
Обязательное правило: При переопределении Equals всегда переопределяйте GetHashCode(), чтобы объекты, считающиеся равными по Equals, возвращали одинаковый хэш-код. Нарушение этого правила приведет к некорректной работе хэш-таблиц.
Пример правильной реализации для класса:
public class Person : IEquatable<Person> // Реализация IEquatable<T> для избежания боксинга
{
public string Name { get; init; }
public int Age { get; init; }
// Реализация для избежания боксинга при сравнении Person с Person
public bool Equals(Person other)
{
if (other is null) return false;
// Используем сравнение строк с учетом/без учета регистра в зависимости от логики
return String.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase)
&& Age == other.Age;
}
// Стандартная перегрузка Equals(object)
public override bool Equals(object obj) => Equals(obj as Person);
// КРИТИЧНО: Согласованный GetHashCode
public override int GetHashCode() => HashCode.Combine(
Name?.ToUpperInvariant(), // Учитываем ту же логику сравнения, что и в Equals
Age
);
// Для удобства можно перегрузить операторы == и !=
public static bool operator ==(Person left, Person right) => Equals(left, right);
public static bool operator !=(Person left, Person right) => !Equals(left, right);
}
// Использование:
var person1 = new Person { Name = "Alice", Age = 30 };
var person2 = new Person { Name = "alice", Age = 30 }; // Та же семантическая сущность
Console.WriteLine(person1.Equals(person2)); // True (благодаря OrdinalIgnoreCase)
Console.WriteLine(person1 == person2); // True (если перегружены операторы)
var set = new HashSet<Person> { person1 };
Console.WriteLine(set.Contains(person2)); // True (работает благодаря корректным Equals и GetHashCode)