Что такое Records в C# 9+? Чем они отличаются от обычных классов?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Records в C# 9.0+
Records — это новый ссылочный тип, представленный в C# 9.0, предназначенный для создания неизменяемых (immutable) объектов данных, которые по умолчанию имеют семантику равенства по значению (value-like equality semantics). Основная философия записей — упростить моделирование данных, которые по своей природе являются неизменяемыми и идентифицируются своим содержимым.
Ключевые характеристики Records
1. Неизменяемость (Immutability)
Записи поощряют использование неизменяемых свойств через синтаксис positional records (позиционные записи), где свойства объявляются непосредственно в заголовке записи.
// Позиционная запись
public record Person(string FirstName, string LastName, int Age);
// Эквивалентный код компилятора создаст класс с init-only свойствами и конструктором
Этот синтаксис автоматически генерирует init-only свойства (с модификатором init вместо set), делая объект неизменяемым после создания.
2. Семантика равенства по значению
В отличие от обычных классов, где равенство по умолчанию основано на сравнении ссылок (ReferenceEquals), записи переопределяют методы Equals, GetHashCode и операторы ==/!= для сравнения всех значений свойств.
var person1 = new Person("Иван", "Иванов", 30);
var person2 = new Person("Иван", "Иванов", 30);
Console.WriteLine(person1 == person2); // True - сравниваются значения
Console.WriteLine(ReferenceEquals(person1, person2)); // False - разные объекты
3. Метод with для неразрушающего изменения (Non-destructive mutation)
Поскольку записи неизменяемы, для "изменения" создается копия с обновленными значениями свойств.
var original = new Person("Иван", "Иванов", 30);
var updated = original with { Age = 31 };
Console.WriteLine(original); // Person { FirstName = Иван, LastName = Иванов, Age = 30 }
Console.WriteLine(updated); // Person { FirstName = Иван, LastName = Иванов, Age = 31 }
4. Автоматически генерируемые методы
Компилятор автоматически генерирует:
- Конструктор для позиционных параметров
- Свойства только для инициализации (init-only)
- Переопределения
ToString(),Equals(),GetHashCode() - Метод
Deconstruct()для деконструкции - Метод
Clone()для поддержкиwith-выражений
// Деконструкция
var (firstName, lastName, age) = person1;
Console.WriteLine(firstName); // Иван
Отличия от обычных классов
| Аспект | Record | Обычный класс |
|---|---|---|
| Цель | Моделирование неизменяемых данных | Универсальное представление объектов |
| Равенство | По значению (сравниваются все данные) | По ссылкам (по умолчанию) |
| Неизменяемость | Поощряется, свойства с init | Не навязывается, свойства с set |
| Изменение | Через with-выражения (новая копия) | Прямое изменение состояния |
| Наследование | Поддерживается, но с ограничениями | Полная поддержка |
| Использование | DTO, Value Objects, сообщения | Бизнес-логика, сервисы, контроллеры |
Типы записей в C# 10+
C# 10 расширил возможности записей:
// Позиционная запись (C# 9)
public record Person(string Name, int Age);
// Структурная запись (C# 10) - value type
public record struct Point(int X, int Y);
// Классовая запись с явным объявлением (C# 10)
public record class Customer(string Id, string Name);
Пример практического использования
// DTO для API
public record ApiResponse<T>(bool Success, T Data, string Error = null);
// Value Object в Domain-Driven Design
public record Money(decimal Amount, string Currency)
{
public static Money operator +(Money a, Money b)
{
if (a.Currency != b.Currency)
throw new InvalidOperationException("Валюты должны совпадать");
return a with { Amount = a.Amount + b.Amount };
}
}
// Использование
var response = new ApiResponse<Person>(true, new Person("Анна", "Смирнова", 28));
var salary = new Money(1000, "USD") + new Money(500, "USD");
Когда использовать Records
- DTO (Data Transfer Objects) — передача данных между слоями
- Value Objects в DDD — объекты, идентифицируемые своими значениями
- Сообщения и события — особенно в event-sourcing и CQRS
- Ключи и составные идентификаторы — где важна семантика равенства
- Конфигурации и настройки — неизменяемые параметры
Ограничения и предостережения
- Записи не заменяют обычные классы для объектов с поведением
- Наследование записей требует осторожности при переопределении равенства
- Для очень больших объектов (десятки свойств) копирование через
withможет быть накладно - Рефлексия и сериализация могут работать иначе из-за сгенерированных методов
Заключение
Records в C# — это мощное дополнение языка, которое заполняет пробел между структурами и классами, предоставляя тип, оптимизированный для работы с неизменяемыми данными. Они сокращают шаблонный код, повышают безопасность благодаря неизменяемости и делают намерения разработчика более явными. Однако важно понимать, что записи — не серебряная пуля, а специализированный инструмент, который следует использовать там, где их семантика соответствует требованиям домена.