Как сравнить два объекта одного класса по содержимому их полей в C#?

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

Ответ

Для глубокого сравнения объектов по значениям их полей и свойств в C# есть несколько стандартных и библиотечных подходов.

1. Использование типа record (C# 9.0 и выше) — самый простой способ Тип record автоматически генерирует логику сравнения по значениям (value-based equality).

public record Person(string FirstName, string LastName, int Age);

var person1 = new Person("John", "Doe", 30);
var person2 = new Person("John", "Doe", 30);
var person3 = new Person("Jane", "Doe", 30);

Console.WriteLine(person1 == person2); // True (значения одинаковы)
Console.WriteLine(person1 == person3); // False
Console.WriteLine(person1.Equals(person2)); // True

2. Ручная реализация через IEquatable<T> и переопределение Equals/GetHashCode Используется для классов (class), где нужно контролировать процесс.

public class Product : IEquatable<Product>
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }

    public bool Equals(Product other)
    {
        if (other is null) return false;
        // Сравниваем все значимые поля. Ссылочные типы сравниваем через Equals.
        return Id == other.Id && 
               Name == other.Name && 
               Price == other.Price;
    }

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

    public override int GetHashCode()
    {
        // HashCode.Combine создает качественный хэш из нескольких значений.
        return HashCode.Combine(Id, Name, Price);
    }

    // Опционально: перегрузка операторов == и !=
    public static bool operator ==(Product left, Product right) => Equals(left, right);
    public static bool operator !=(Product left, Product right) => !Equals(left, right);
}

3. Использование IEqualityComparer<T> для внешней логики сравнения Полезно, когда нужно несколько разных стратегий сравнения для одного типа, или если нельзя изменить исходный класс.

public class ProductNameComparer : IEqualityComparer<Product>
{
    public bool Equals(Product x, Product y) => x?.Name == y?.Name;
    public int GetHashCode(Product obj) => obj.Name?.GetHashCode() ?? 0;
}
// Использование в LINQ
var distinctProducts = products.Distinct(new ProductNameComparer());

4. Рефлексия или библиотеки для сложных графов объектов Для глубокого сравнения сложных объектов с вложенными коллекциями можно использовать:

  • Рефлексию: Медленно, но универсально. Не рекомендуется для высоконагруженного кода.
  • Библиотеки: AutoMapper (проекция и сравнение), CompareNETObjects (специализированная библиотека для детального сравнения).

Рекомендация: Для новых типов данных, где важна семантика сравнения по значению, используйте record. Для существующих классов реализуйте IEquatable<T> с HashCode.Combine.