Ответ
Record — это новый ссылочный тип (record class, по умолчанию) или тип-значение (record struct), представленный в C# 9.0 и улучшенный в последующих версиях. Его основное предназначение — моделирование неизменяемых (immutable) данных с семантикой равенства по значению (value-based equality).
Ключевые характеристики и "магия" компилятора:
- Неизменяемость по умолчанию (Positional Records): Свойства, объявленные в первичном конструкторе, автоматически становятся
init-only(инициализируются при создании и не могут быть изменены). - Равенство по значению: Для records автоматически генерируются методы
Equals(object?),GetHashCode()и операторы==/!=, которые сравнивают значения всех свойств, а не ссылки на объект. - Метод
ToString(): Автоматически генерирует удобное строковое представление, включающее имя типа и значения свойств. - Поддержка деконструкции (Deconstruction): Для positional records генерируется метод
Deconstruct, позволяющий легко разложить объект на переменные. - Выражение
with(Non-destructive mutation): Позволяет создать копию record с изменением одного или нескольких свойств, не изменяя оригинал.
Практические примеры:
// 1. Positional record (самый частый вариант)
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 = person1 with { Age = 31 }; // Создание модифицированной копии
Console.WriteLine(person1 == person2); // True (равенство по значению)
Console.WriteLine(person1); // Person { FirstName = John, LastName = Doe, Age = 30 }
Console.WriteLine(person3); // Person { FirstName = John, LastName = Doe, Age = 31 }
var (fName, lName, age) = person1; // Деконструкция
// 2. Record с явно объявленными свойствами (если нужна валидация или логика)
public record Product
{
public Guid Id { get; init; } // init-only для неизменяемости
public string Name { get; init; }
public decimal Price { get; init; }
public Product(string name, decimal price)
{
if (price < 0) throw new ArgumentException("Price must be non-negative.");
Id = Guid.NewGuid();
Name = name;
Price = price;
}
}
// 3. Record struct (C# 10+)
public readonly record struct Point(int X, int Y); // readonly для полной неизменяемости
Идеальные сценарии использования:
- DTO (Data Transfer Objects) для API-запросов и ответов.
- Модели команд и событий в архитектурах CQRS/Event Sourcing.
- Ключи в словарях (благодаря корректно реализованным
EqualsиGetHashCode). - Любые объекты-значения, где важна неизменяемость и сравнение по содержанию.
Ответ 18+ 🔞
Давай разберемся с этими вашими record'ами, а то народ местами нихуя не понимает, что это за зверь такой и зачем он нужен.
Record — это, по сути, новый тип в C#, который появился в девятой версии и потом ещё допилили. Если коротко — это специальная форма класса или структуры, которая заточена под хранение данных, которые менять не планируется. Главная фишка — равенство по значению работает из коробки, без этой всей ебалы с переопределением Equals и GetHashCode.
Что там за магия происходит под капотом:
- Неизменяемость (в основном варианте): Свойства, которые ты объявляешь в конструкторе, становятся
init-only. То есть задал раз при создании — и всё, потом нихуя не поменяешь. Удобно, чтобы случайно не накосячить. - Равенство по значению, а не по ссылке: Это, блядь, самое важное. Два record'а считаются равными, если у них все свойства попарно равны. Компилятор сам генерит все нужные методы сравнения, не надо руками это городить. Просто красота.
- ToString() который не сосёт: Автоматом выводит что-то вменяемое типа
Person { FirstName = John, LastName = Doe, Age = 30 }, а не название типа и хеш-код, от которого толку ноль. - Деконструкция на раз-два: Можно сразу разобрать объект на составные части в переменные, без лишних телодвижений.
- Выражение
with— копия с изменениями: Вот это вообще пиздец как удобно. Хочешь взять существующий объект, поменять в нём одно поле, а остальные оставить как есть, и чтобы оригинал не пострадал? Легко! Создаёшь копию черезwith. Никакого геморроя с клонированием.
Смотри, как это выглядит на практике:
// 1. Классический record (positional record)
public record Person(string FirstName, string LastName, int Age);
// Создаём
var ivan = new Person("Иван", "Иванов", 30);
var anotherIvan = new Person("Иван", "Иванов", 30);
// Меняем возраст, создавая нового Ивана
var ivanOlder = ivan with { Age = 31 };
// Магия равенства работает
Console.WriteLine(ivan == anotherIvan); // True! Потому что свойства одинаковые.
Console.WriteLine(ivan); // Выведет: Person { FirstName = Иван, LastName = Иванов, Age = 30 }
Console.WriteLine(ivanOlder); // Выведет: Person { FirstName = Иван, LastName = Иванов, Age = 31 }
// Деконструкция — разобрал как лего
var (name, surname, years) = ivan;
// 2. Record с кастомным конструктором (если надо валидацию или логику впихнуть)
public record Product
{
public Guid Id { get; init; } // init-only — менять нельзя
public string Name { get; init; }
public decimal Price { get; init; }
public Product(string name, decimal price)
{
if (price < 0) throw new ArgumentException("Цена не может быть отрицательной, ты чё, охренел?");
Id = Guid.NewGuid();
Name = name;
Price = price;
}
}
// 3. Record struct (появился в C# 10)
public readonly record struct Point(int X, int Y); // readonly чтобы наверняка ничего не сломал
Где это самое оно применять:
- Объекты для передачи данных (DTO) в API — туда-сюда таскать, идеально.
- Команды и события в CQRS или Event Sourcing — там неизменяемость прям просится.
- Ключи в словарях — раз равенство по значению работает, то и в
Dictionaryвсё будет как надо. - Вообще любые объекты, которые по смыслу являются "значениями" и которые нужно сравнивать по содержимому, а не по тому, где они в памяти лежат.