В C#: в чем разница между `==` и `Equals()` при сравнении объектов?
Ключевое различие: сравнение по ссылке vs по значению
В C# == и Equals() ведут себя по-разному в зависимости от типа данных. Ваша проблема — классический пример путаницы между сравнением ссылок и сравнением содержимого объектов.
1. Поведение для значимых типов (value types)
Для структур (struct), таких как int, double, DateTime, и для строк (string — особый ссылочный тип с перегруженным поведением):
==сравнивает значения полей.5 == 5— true.Equals()делает то же самое — сравнивает содержимое.
Поэтому int a = 5; int b = 5; a == b даёт true.
2. Поведение для ссылочных типов (reference types) по умолчанию
Для классов (как ваш Person) по умолчанию:
==сравнивает ссылки (адреса в памяти). Два разных объекта — разные ссылки, даже если содержимое одинаковое.Equals()(унаследованный отObject) делает то же самое — сравнивает ссылки.
Именно поэтому p1 == p2 и p1.Equals(p2) возвращают false — это разные объекты в куче.
3. Исключение: строки (string)
Строки — ссылочный тип, но для них == и Equals() переопределены для сравнения по содержимому. Поэтому "hello" == "hello" — true. Это сделано для удобства, так как строки часто сравнивают по значению.
4. Как исправить ваш код: переопределить Equals и ==
Чтобы сравнивать объекты Person по содержимому, нужно переопределить методы Equals() и оператор ==:
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()
{
// Важно: если переопределяете Equals, переопределите и GetHashCode
return HashCode.Combine(Name, Age);
}
public static bool operator ==(Person p1, Person p2)
{
if (ReferenceEquals(p1, p2))
return true;
if (p1 is null || p2 is null)
return false;
return p1.Equals(p2);
}
public static bool operator !=(Person p1, Person p2)
{
return !(p1 == p2);
}
}
Теперь:
Person p1 = new Person { Name = "Alice", Age = 30 };
Person p2 = new Person { Name = "Alice", Age = 30 };
Console.WriteLine(p1 == p2); // true
Console.WriteLine(p1.Equals(p2)); // true
5. Практические рекомендации
- Используйте
==для сравнения простых значений (int, bool, double) и строк — это читаемо и эффективно. - Используйте
Equals(), когда работаете с полиморфизмом, коллекциями (например,List.Contains()используетEquals()), или когда нужно сравнить объект сnull. - Для своих классов всегда переопределяйте
Equals()иGetHashCode()вместе, если планируете сравнивать объекты по значению. Операторы==/!=переопределяйте только если это действительно нужно для читаемости кода.
Запомните золотое правило: ссылочные типы сравниваются по ссылкам, если не переопределено иное. Строки — исключение, но для своих классов вы сами решаете, что значит "равно".
<<Альтернативный взгляд: когда переопределять == может быть не нужно
В некоторых случаях переопределять оператор == для класса не обязательно. Например, если вы используете record (C# 9+), то компилятор автоматически генерирует сравнение по значению для всех свойств:
public record Person(string Name, int Age);
var p1 = new Person("Alice", 30);
var p2 = new Person("Alice", 30);
Console.WriteLine(p1 == p2); // true — автоматически
Console.WriteLine(p1.Equals(p2)); // true
Также, если вы работаете с LINQ или коллекциями (например, Distinct(), Contains()), они используют Equals() и GetHashCode(). Поэтому для таких сценариев достаточно переопределить только эти два метода, а == оставить как есть (сравнение по ссылке) — это не сломает логику, если вы не используете оператор напрямую.
Ещё один важный нюанс: производительность. Оператор == для значимых типов работает быстрее, чем вызов виртуального метода Equals(). Но для ссылочных типов разница обычно незначительна. Главное — ясность кода и соблюдение контракта (например, если вы помещаете объект в Dictionary, GetHashCode должен быть согласован с Equals).
Ответов пока нет.
Войдите чтобы оставить ответ