Урок по треку C# и .NET 7 мин

Типы значений и ссылочные типы

struct и class, стек и куча, boxing, Nullable, record и передача параметров.

Вопрос «чем struct отличается от class» звучит почти на каждом .NET-собесе. Хороший ответ начинается не со стека и кучи, а с семантики копирования.

Два вида типов

Типы значений (int, double, bool, decimal, DateTime, любые struct и enum): переменная содержит сами данные. Присваивание копирует их целиком.

Ссылочные типы (class, string, массивы, делегаты, record): переменная содержит ссылку на объект в куче. Присваивание копирует ссылку, и обе переменные указывают на один объект.

struct PointS { public int X; }
class PointC { public int X; }

var a = new PointS { X = 1 };
var b = a;      // копия данных
b.X = 5;        // a.X по-прежнему 1

var c = new PointC { X = 1 };
var d = c;      // копия ссылки
d.X = 5;        // c.X тоже 5

Где что хранится

Объекты классов всегда в управляемой куче. Тип значения хранится там, где лежит его переменная: локальная переменная обычно на стеке (или в регистре), поле класса внутри объекта в куче, элемент массива внутри массива в куче. Поэтому фраза «структуры живут на стеке» неточна, и интервьюер это любит проверять.

Локальные переменные тоже могут уехать в кучу: если их захватила лямбда (появится объект-замыкание) или они живут через await (станут полями машины состояний).

Передача параметров

По умолчанию всё передаётся по значению: для структуры копируются данные, для класса копируется ссылка. Метод может менять поля переданного объекта, но присваивание параметру p = new Person() вызывающий не увидит.

  • ref: ссылка на саму переменную, нужна инициализация до вызова.
  • out: метод обязан присвоить значение.
  • in: ссылка только для чтения, удобно для больших структур без копирования.

Boxing и unboxing

Упаковка происходит, когда тип значения приводится к object или интерфейсу: в куче создаётся объект-копия. Распаковка требует точного типа.

object o = 42;          // boxing
int x = (int)o;         // unboxing
long y = (long)o;       // InvalidCastException: упакован int, а не long

Минусы: аллокации и нагрузка на GC, а изменения копии не видны оригиналу. Источники скрытой упаковки: ArrayList, вызов метода интерфейса у структуры через переменную интерфейсного типа, == у переменных типа object. Дженерики (List<int>) появились во многом ради того, чтобы упаковки не было.

Nullable<T>

int? это структура Nullable<int> с полями HasValue и Value. Арифметика «поднятая»: если хотя бы один операнд null, результат null. Сравнения <, > с null всегда false. .Value у пустого значения бросает InvalidOperationException, безопаснее ?? и GetValueOrDefault().

Не путайте с nullable reference types (string?): это лишь аннотации для анализатора компилятора, во время выполнения string и string? одинаковы.

record

record (класс) и record struct генерируют равенство по значению, ToString, Deconstruct и поддерживают with:

record User(string Name, int Age);
var a = new User("Ира", 30);
var b = a with { Age = 31 };   // новая копия
Console.WriteLine(a == new User("Ира", 30)); // True

Когда struct

Структура подходит для маленького (ориентир до 16-24 байт) неизменяемого значения без идентичности: координата, деньги, диапазон дат. Делайте её readonly struct. Изменяемые структуры коварны: list[0].X = 1 не компилируется, потому что индексатор возвращает копию, а у readonly-поля метод вызовется на защитной копии.

Частые ошибки

  • Считать, что присваивание структуры даёт «ту же» структуру.
  • Сравнивать упакованные значения через == у object: сравниваются ссылки.
  • Ожидать, что default(S) или new S[10] вызовет конструктор структуры без параметров. Не вызовет.

Что спросят на собесе

  • Чем тип значения отличается от ссылочного и где они хранятся?
  • Что такое boxing, где он происходит незаметно и чем вреден?
  • Как передать ссылочный тип так, чтобы метод мог заменить объект у вызывающего?
  • Чем record отличается от class и когда выбрать struct?
  • Что такое защитная копия и зачем readonly struct?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.