JavaRush /Курсы /C# SELF /Отличия record от class и struct

Отличия record от class и struct

C# SELF
19 уровень , 4 лекция
Открыта

1. Почему вообще понадобились record

В C# (и .NET в целом) долгое время основными строительными блоками были классы (class) и структуры (struct). Но каждый из них неполон для ряда задач. Классы — ссылочные, мутируемые, и сравнение у них по ссылке (за очень редким исключением). Структуры — значимые типы (копируются при передаче), по умолчанию сравниваются побайтово и чаще всего мутируемы (пока не появились readonly struct).

А вот если вам хотелось бы простую и натуральную запись данных, которую удобно сравнивать по значению, быстро клонировать и не париться о том, что ваш объект кто-то где-то изменил — приходилось городить кучу велосипедов или прибегать к библиотекам наподобие ValueTuple или даже System.Tuple. Но все они не так красивы и выразительны, как хотелось бы.

Поэтому в C# 9 появился record — тип данных, который сочетает краткость объявления, безопасность неизменяемости и поведение, ориентированное на сравнение по значению.

2. Все четыре типа на одной страничке

class struct record record struct
Категория Ссылочный тип Значимый тип Ссылочный тип Значимый тип
Мутируемость По умолчанию — да По умолчанию — да По умолчанию — нет (init) По умолчанию — нет (init)
Сравнение По ссылке (==) По значению (поля) По значению (fields/properties) По значению (fields)
Клонирование Только вручную Только вручную Есть встроенная поддержка (with) Есть встроенная поддержка (with)
Наследование Да Нет Да Нет
Неизменяемость Нужно реализовать Нужно реализовать Очень просто реализовать Очень просто реализовать
Синтаксис Самый длинный Короткий Самый короткий (позиционный) Довольно короткий
Использование в коллекциях По ссылке Копии По ссылке Копии

Визуальная схема

+----------------+     +----------------+     +--------------------+
|    class       |     |    struct      |     |      record        |
+----------------+     +----------------+     +--------------------+
| Reference Type |     | Value Type     |     | Reference Type     |
| Mutable        |     | Mutable        |     | Immutable (init)   |
| == : Reference |     | == : By Fields |     | == : By Value      |
+----------------+     +----------------+     +--------------------+

3. Суть различий между record, class и struct

Поведение в памяти: ссылка или значение?

  • class и record — это ссылочные типы. Когда вы их передаёте в функцию, копируется ссылка на объект.
  • struct и record struct — значимые типы. Они всегда копируются побайтово (если только вы не передаёте их по ссылке явно).

class PointClass { public int X; public int Y; }
struct PointStruct { public int X; public int Y; }
record PointRecord(int X, int Y);
record struct PointRecordStruct(int X, int Y);

void Demo()
{
    var pc = new PointClass { X = 1, Y = 2 };
    var ps = new PointStruct { X = 1, Y = 2 };
    var pr = new PointRecord(1, 2);
    var prs = new PointRecordStruct(1, 2);

    ChangeY(pc);    // pc.Y изменится!
    ChangeY(ps);    // ps.Y не изменится — копия!
    ChangeY(pr);    // pr.Y изменится!
    ChangeY(prs);   // prs.Y не изменится — копия!
}

void ChangeY(dynamic p) { p.Y = 99; }

Если вам кажется, что dynamic тут это магия — да, это просто для примера, чтобы вы увидели, что при работе со структурами значение не изменится, а при работе с классом или record (ссылочным) — изменится.

Сравнение: как узнать, что два объекта равны?

  • class: сравниваются по ссылке (== — true, только если это один и тот же объект в памяти), если не переопределить Equals.
  • struct: сравниваются по значению всех полей (по умолчанию).
  • record: сравниваются по значению всех полей/свойств, которые заданы в первичном конструкторе.
class Foo { public int A; public int B; }
record Bar(int A, int B);

var foo1 = new Foo { A = 42, B = 1 };
var foo2 = new Foo { A = 42, B = 1 };
var bar1 = new Bar(42, 1);
var bar2 = new Bar(42, 1);

Console.WriteLine(foo1 == foo2); // False! Разные объекты
Console.WriteLine(bar1 == bar2); // True! Значения совпали

Интересный факт:
С record struct всё ещё круче: у них сравнение "по значению", как у обычных struct, но с синтаксисом и возможностями record.

4. Неизменяемость: кто сколько готов гарантировать?

Сравниваем безопасность объектов:

  • class: По умолчанию легко изменяем, если явно не сделать все поля readonly.
  • struct: То же самое, но можно объявить как readonly struct, тогда ни поля, ни свойства нельзя менять.
  • record: Обычно поля объявляются с модификатором init, то есть задать их можно только в конструкторе или в инициализаторе объекта (with). Это удобно для безопасной передачи данных.
  • record struct: То же, можно сделать readonly record struct и получать неизменяемость значимых типов с бесплатными плюшками record.

record Person(string Name, int Age);

var p1 = new Person("Алексей", 23);
// p1.Age = 24; // Ошибка! Только init

var p2 = p1 with { Age = 24 }; // Работает! Создаёт копию с новыми данными

Когда у вас большой проект и десятки entity-объектов, record позволят избежать кучи багов, связанных с "кто-то где-то поменял поле — и всё сломалось".

5. Синтаксис: как выглядят объявления и как не запутаться


// class
public class Product
{
    public int Id { get; init; }
    public string Name { get; init; }
}

// struct
public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

// record
public record Product(int Id, string Name);

// record struct
public record struct Point(int X, int Y);
Сравнение синтаксиса объявления разных типов

Как видите, запись максимально короткая у record-типа. Получаете конструктор, деструктор, равенство, ToString() и многое другое "из коробки".

Синтаксис Получаете бесплатно Можно ли наследовать
class ничего (почти) Да
struct ничего (почти) Нет
record ToString, Equals, Deconstruct, with Да
record struct ToString, Equals, Deconstruct, with Нет

6. Клонирование и оператор with

Только у record и record struct есть специальный оператор with, который позволяет без боли копировать объект с изменением отдельных свойств.


record User(string Name, int Age);

var user1 = new User("Ирина", 28);
var user2 = user1 with { Age = 29 }; // user2.Name == "Ирина", user2.Age == 29

В случае класса (class) придётся реализовывать метод копирования вручную, а если забыть скопировать одно из полей — привет, баги.

7. Наследование: кто кого

  • class: Поддерживает стандартное наследование (иерархии классов, виртуальные методы, абстракции и т.д.).
  • struct: Не поддерживает (может только реализовывать интерфейсы).
  • record: Поддерживает наследование, но с некоторыми ограничениями (например, наследование возможно только между record-типами, не с class).
  • record struct: Не поддерживает, как и обычные struct.
record Animal(string Name);
record Dog(string Name, string Breed) : Animal(Name); // Окей!

class Vehicle { }
class Car : Vehicle { } // Окей!

// struct не может унаследоваться от struct

Подробнее о наследовании в следующем уровне :P

8. Где какие типы применяются на практике

  • class: Большие объекты с богатым поведением, длинным жизненным циклом, изменяемым состоянием, иерархией (например, представление бизнес-логики, UI-компоненты).
  • struct: Мелкие объекты-значения, где важно максимально быстрое копирование, отсутствие GC и минимальный оверхед (например, координаты, цвета, суммы — то, что легко и быстро клонировать).
  • record: DTO (Data Transfer Object), объекты-параметры, параметры конфигурации, неизменяемые состояния, результаты вычислений, которым удобно сравниваться по содержимому.
  • record struct: Значимые типы, где нужна неизменяемость и поведение, как у records, но без лишних аллокаций в куче.

9. Типичные ошибки и подводные камни

Иногда программисты думают, что record — это просто "замена класса". Это не так! Если вы пишете объект, который должен менять своё состояние в процессе жизни — используйте class.
Если вы хотите сравнивать объекты по ссылке (например, в паттерне "одиночка" или когда жизненный цикл объекта критичен) — используйте class.
Если вы делаете объект-значение, который должен вести себя как число или точка на координатной сетке — struct или record struct.
Если вы работаете с неизменяемыми, легко сравниваемыми объектами, которые часто передаются между слоями приложения, хранятся в коллекциях, логируются и сериализуются — ваш выбор record.

Также помните: если вы объявили record-класс с только-читабельными свойствами, но забыли про вложенные объекты — вложенные поля всё равно могут быть изменены, если они сами мутируемы.


record Student(string Name, List<int> Grades);

var s1 = new Student("Антон", new List<int>() {5,5,5});
var s2 = s1 with { };

s2.Grades.Add(2); // Оба объекта "прокляты" двойкой! s1.Grades == s2.Grades
2
Задача
C# SELF, 19 уровень, 4 лекция
Недоступна
Создание record и сравнение объектов
Создание record и сравнение объектов
2
Задача
C# SELF, 19 уровень, 4 лекция
Недоступна
Использование оператора with для записи данных
Использование оператора with для записи данных
1
Опрос
Проблема с DTO, 19 уровень, 4 лекция
Недоступен
Проблема с DTO
DTO, record и with
Комментарии (6)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Ra Уровень 35 Student
26 ноября 2025
Наворотили-то сущностей, сами не путаются в этом? 😀
Александр Уровень 39
10 января 2026
record реально крутая вещь вместо DTO, но надо понимать контекст использования. Тут бегут по верхам
Serhii Pypa Уровень 63
27 октября 2025
Начиная с версии C# 12 для структур, как и для классов, можно определять первичные конструкторы. Первичные конструкторы позволяют добавлять параметры к определению класса/структуры и использовать эти параметры внутри класса/структуры: public struct Person(string name, int age) { public Person(string name) : this(name, 18) { } public void Print() => Console.WriteLine($"name: {name}, age: {age}"); } var tom = new Person("Tom", 38); tom.Print();
Дмитрий Уровень 14
1 сентября 2025
Задача 19 уровень, 1 лекция Сравнение record по значению одна и та же задача с задачей 1 из этой лекции. Вообще пока складывается ощущение, что все задачи - просто повторение примеров из лекций. Сложность задач максимально простая, не сравнить с курсом Java.
whynot00 Уровень 1
24 сентября 2025
вообще этот курс выглядит как скам лютый
Slevin Уровень 57
2 февраля 2026
Это вы еще курса по Пайтону не видели. Вот где шляпа такая, что ппц.