Какой тип данных у параметра, который принимает метод Equals (унаследованный от Object)?

Ответ

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

Сигнатура:

public virtual bool Equals(object? obj)

Пример переопределения в пользовательском классе:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }

    public override bool Equals(object obj)
    {
        // 1. Проверка на null и совпадение типов
        if (obj == null || GetType() != obj.GetType())
            return false;

        // 2. Приведение типа и сравнение значимых полей
        Product other = (Product)obj;
        return Id == other.Id && Name == other.Name;
    }

    // Переопределение GetHashCode обязательно при переопределении Equals
    public override int GetHashCode() => HashCode.Combine(Id, Name);
}

Проблема и улучшение: Поскольку параметр — object, необходимы проверки типа и приведение, что снижает производительность из-за упаковки (для структур) и динамических проверок. Для решения этой проблемы в .NET введён интерфейс IEquatable<T>.

Рекомендация: Всегда реализуйте IEquatable<T> вместе с переопределением Object.Equals для типов-значений (struct) и часто сравниваемых ссылочных типов (class). Метод IEquatable<T>.Equals принимает типизированный параметр (T), что делает сравнение безопасным и эффективным.

Ответ 18+ 🔞

Давай разберём эту тему, как будто я тебе на кухне объясняю, за рюмкой чая. Смотри, есть у нас в C# такой старый, как говно мамонта, метод — Equals. Он от прародителя всех классов, System.Object, достался. И принимает он, внимание, object. То есть, что угодно, хоть табуретку, хоть соседа Васю.

public virtual bool Equals(object? obj)

Представь: у тебя есть класс Product — ну, товар там, с айдишником и именем. И ты хочешь сравнивать товары не по тому, одна ли это ссылка в памяти, а по смыслу: совпадают ли айди и имя.

Вот как это выглядит, когда ты его переопределяешь:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }

    public override bool Equals(object obj)
    {
        // 1. А не null ли нам подсунули? И того же ли мы вида?
        if (obj == null || GetType() != obj.GetType())
            return false;

        // 2. Теперь, если мы здесь, можно безопасно привести тип и посмотреть на поля
        Product other = (Product)obj;
        return Id == other.Id && Name == other.Name;
    }

    // А это ОБЯЗАТЕЛЬНО, иначе всё пойдёт по пизде! GetHashCode надо переопределять в паре с Equals.
    public override int GetHashCode() => HashCode.Combine(Id, Name);
}

Вроде логично, да? Но тут есть подвох, и он жирный, как жопа бегемота. Метод принимает object. Это значит, что если у тебя структура (struct), то при каждом вызове этого Equals произойдёт упаковка — обёртывание в объект, а это, на минуточку, лишние телодвижения для процессора и мусор для сборщика. Да и для классов постоянные проверки GetType() и приведение — тоже не подарок.

И тут на сцену выходит, блядь, спаситель — интерфейс IEquatable<T>. Он как умный родственник, который говорит: «Хули ты каждый раз объект в тапки заворачиваешь? Дай мне сразу конкретный тип!».

Рекомендация железная: если пишешь свой тип-значение (struct) или часто сравниваемый ссылочный тип (class), реализуй IEquatable<T> ВМЕСТЕ с переопределением старого Object.Equals. Метод IEquatable<T>.Equals принимает уже типизированный параметр (T), и там ни упаковки, ни лишних танцев с бубном. Безопасно, эффективно, и совесть чиста.

Вот и вся магия. А то смотришь на код, где Equals переопределён, а IEquatable<T> нет — и волосы дыбом встают, как будто человек пешком из Москвы в Питер собрался, когда есть «Сапсан».