Как сравниваются ссылочные типы в C#?

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

Ответ

Сравнение ссылочных типов в C# зависит от используемого оператора или метода. По умолчанию сравнение идет по ссылке (адресу в памяти), но это поведение можно переопределить для сравнения по значению.

1. Сравнение по ссылке (Reference Equality)

  • Оператор ==: Для большинства ссылочных типов (кроме string) сравнивает, указывают ли две переменные на один и тот же объект в куче.
  • Статический метод object.ReferenceEquals(a, b): Явно сравнивает ссылки.
var obj1 = new object();
var obj2 = new object();
var obj3 = obj1;

Console.WriteLine(obj1 == obj2); // False (разные объекты)
Console.WriteLine(obj1 == obj3); // True (одна и та же ссылка)
Console.WriteLine(ReferenceEquals(obj1, obj2)); // False

2. Сравнение по значению (Value Equality)

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

public class Person : IEquatable<Person>
{
    public string Name { get; set; }

    public bool Equals(Person other)
    {
        if (other is null) return false;
        return Name == other.Name;
    }

    public override bool Equals(object obj) => Equals(obj as Person);
    public override int GetHashCode() => Name?.GetHashCode() ?? 0;
}

// Использование
var person1 = new Person { Name = "Alice" };
var person2 = new Person { Name = "Alice" };

Console.WriteLine(person1 == person2); // False (по-прежнему сравнение ссылок, если не перегружен оператор ==)
Console.WriteLine(person1.Equals(person2)); // True (используется наша логика сравнения по значению)

3. Особый случай: string

Класс string является ссылочным типом, но операторы == и != в C# для него перегружены для сравнения значений, а не ссылок. Это поведение задано на уровне языка.

Рекомендация: Для универсального и безопасного сравнения значений используйте EqualityComparer<T>.Default.Equals(a, b). Этот метод автоматически использует корректную логику сравнения: IEquatable<T>, переопределенный Equals или сравнение ссылок.