Встречается на 33% собеседований по C#

Чем отличается метод Equal от ==

Метод Equals и оператор == в C# используются для сравнения объектов, но имеют принципиальные различия в поведении по умолчанию и возможностях переопределения. == — это оператор, который для ссылочных типов по умолчанию проверяет равенство ссылок (указывают ли переменные на один и тот же объект в памяти), а для значимых типов — равенство значений. Equals — это виртуальный метод класса Object, который также по умолчанию для ссылочных типов сравнивает ссылки, но его можно переопределить в производных классах для реализации собственной логики сравнения по значению или другим критериям.

Как работает ==

Оператор == является статическим и не может быть вызван через экземпляр. Для значимых типов (например, int, double) он сравнивает значения. Для ссылочных типов (классов) он по умолчанию сравнивает ссылки, то есть возвращает true, только если обе переменные указывают на один и тот же объект. Однако оператор == можно переопределить для пользовательских типов, чтобы изменить его поведение, например, включить сравнение по значению. Переопределение обычно выполняется вместе с оператором !=.

Как работает Equals

Метод Equals определен в Object и может быть переопределен в любом классе. Его сигнатура: public virtual bool Equals(object obj). Для значимых типов базовая реализация сравнивает значения, для ссылочных — ссылки. Переопределение Equals позволяет задать семантическое равенство: например, два объекта Person могут считаться равными, если у них совпадают Name и Age, даже если это разные экземпляры. При переопределении Equals необходимо также переопределить GetHashCode, чтобы объекты, равные по Equals, имели одинаковый хэш-код (это требование для корректной работы в хэш-таблицах, например, Dictionary).

Пример переопределения

csharp
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }

    public override bool Equals(object obj)
    {
        if (obj == null || GetType() != obj.GetType())
            return false;

        Person other = (Person)obj;
        return Name == other.Name && Age == other.Age;
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(Name, Age);
    }

    public static bool operator ==(Person left, Person right)
    {
        if (ReferenceEquals(left, right))
            return true;

        if (left is null || right is null)
            return false;

        return left.Equals(right);
    }

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

В этом примере и Equals, и == переопределены так, что сравнивают объекты Person по значениям свойств Name и Age. Обратите внимание, что в Equals проверяется тип через GetType(), чтобы избежать сравнения с объектами других типов.

Подводные камни

  • Если переопределить Equals, но не переопределить GetHashCode, объекты могут некорректно работать в коллекциях, использующих хэширование (например, HashSet, Dictionary).
  • Если переопределить ==, но не переопределить Equals, поведение может стать несогласованным: == будет использовать одну логику, а Equals — другую. Рекомендуется переопределять их согласованно.
  • Для значимых типов оператор == и метод Equals по умолчанию работают одинаково, но для пользовательских структур (struct) переопределение == требует явного определения оператора.
  • При сравнении с null через == для ссылочных типов не возникает исключения, а Equals может выбросить NullReferenceException, если вызвать его на null (но в переопределении обычно проверяют obj == null).

Когда что использовать

  • Используйте == для проверки идентичности ссылок, если не требуется переопределение, или когда вы переопределили оператор для своего типа.
  • Используйте Equals для сравнения по значению, особенно когда вы переопределили его в классе. Equals также используется в таких методах, как List.Contains, Dictionary.ContainsKey и других.
  • В большинстве случаев для пользовательских классов, где важна семантика равенства, переопределяют оба: Equals и == (и !=), чтобы поведение было единообразным.

Коротко

  • == — оператор, по умолчанию сравнивает ссылки для классов и значения для структур; переопределяется через operator ==.
  • Equals — виртуальный метод, по умолчанию сравнивает ссылки для классов; переопределяется для сравнения по значению.
  • При переопределении Equals всегда переопределяйте GetHashCode.
  • Для согласованности переопределяйте == и Equals вместе, чтобы они давали одинаковый результат.
как отвечать на вопрос
пример собеседования
фреймворки на собеседовании
типичные вопросы junior
интервью вопросы и ответы