Для чего переопределяют методы GetHashCode и Equals в C#?

«Для чего переопределяют методы GetHashCode и Equals в C#?» — вопрос из категории C# Core, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Переопределение Equals и GetHashCode критически важно для корректной работы объектов в хэш-базированных коллекциях (Dictionary<TKey, TValue>, HashSet<T>) и для определения логического, а не ссылочного равенства.

Основные причины:

  1. Логическое равенство (Equals): Позволяет сравнивать объекты по значению их полей, а не по ссылке в памяти.
  2. Контракт с хэш-таблицами (GetHashCode): Хэш-код используется для быстрого определения "ведра" (bucket), в котором хранится объект. Нарушение контракта между Equals и GetHashCode приводит к непредсказуемому поведению коллекций.

Ключевой контракт: Если два объекта равны по Equals, они обязаны возвращать одинаковый хэш-код через GetHashCode. Обратное не обязательно верно (коллизии хэшей допустимы).

Пример реализации:

public class Product : IEquatable<Product>
{
    public int Id { get; init; }
    public string Name { get; init; }

    // Реализация для производительности и типобезопасности
    public bool Equals(Product other)
    {
        if (other is null) return false;
        return Id == other.Id && Name == other.Name;
    }

    public override bool Equals(object obj) => Equals(obj as Product);

    public override int GetHashCode()
    {
        // Используем HashCode.Combine для генерации качественного хэша
        return HashCode.Combine(Id, Name);
    }
}

Best Practices:

  • Переопределяйте оба метода вместе.
  • Используйте неизменяемые (immutable) поля для расчета хэш-кода. Если объект может измениться после добавления в HashSet, его хэш-код должен оставаться стабильным, иначе он станет "потерянным" в коллекции.
  • Для простых сценариев используйте HashCode.Combine (доступен с .NET Core 2.1/C# 8.0).
  • Рассмотрите реализацию IEquatable<T> для повышения производительности при сравнении объектов одного типа.