Два вида типов
Чему вы научитесь:
- отличать значимые типы от ссылочных и предсказывать, что копирует присваивание;
- объявлять структуры и передавать в методы структуры и объекты, с
refи без; - объяснять упаковку и работать с
nullчерез?.,??,int?иstring?; - сравнивать объекты и структуры через
==иEquals.

Коллеге нужен ваш отчёт, и отдать его можно двумя способами. Можно сделать ксерокопию: у коллеги будет свой лист, он может исчёркать его как угодно, а ваш останется прежним. А можно прислать ссылку на документ в облаке. Тогда вы оба открываете один и тот же документ, и правку одного сразу видит другой.
В C# присваивание и передача аргумента в метод тоже работают одним из этих двух способов. Оба вы уже встречали: числа при присваивании копировались, а массивы и объекты классов получали второе имя. Посмотрим на них рядом:
int a = 1;
int b = a;
b = 2;
Console.WriteLine(a); // 1
Counter first = new Counter();
Counter second = first;
second.Value = 2;
Console.WriteLine(first.Value); // 2
class Counter
{
public int Value;
}Давайте разберём пример по строкам:
int b = a;делает ксерокопию: вbпопадает своё число1.b = 2меняет толькоb, поэтомуaпо-прежнему печатает1.Counter second = first;копирует не объект, а ссылку на него. Это вторая ссылка на тот же документ.second.Value = 2меняет общий объект, иfirst.Valueтоже печатает2.
Одно и то же присваивание = дало два разных результата.
Здесь аналогия неточна. В жизни ксерокопию делают, только когда о ней попросят. В C# выбора при присваивании нет: копируется ли значение или ссылка, решает тип, а не тот, кто пишет =.
Значимые типы
Переменная значимого типа хранит само значение. Присваивание и передача в метод копируют его целиком, и копии дальше живут отдельно. Вы уже встречали это поведение:
- в теме о переменных: после
b = aиa = 7вbоставалось старое число; - в теме о методах: метод получал копию аргумента, а саму переменную — через
ref.
Значимые типы — это все встроенные числовые типы (int, long, double, decimal), а также bool и char. Свой значимый тип тоже можно объявить, но словом struct, а не class.
Ссылочные типы
Переменная ссылочного типа хранит ссылку, а сам объект лежит в памяти отдельно. Присваивание копирует ссылку, и у объекта становится на одно имя больше. Ссылочные типы вам тоже знакомы:
- массивы: после
int[] b = a;у одного массива два имени; string: одну строку могут делить несколько переменных, и это безопасно, потому что строку никто не изменит;- все классы: переменная класса хранит ссылку на объект.
Вид типа зависит от его объявления. Тип, объявленный словом class, всегда ссылочный, и никакое присваивание не сделает из его объекта ксерокопию.
Стек и куча
Откуда берётся разница? Чтобы это понять, посмотрим, где программа хранит данные. Упрощённая картина такая:
- стек — уже знакомый вам стек вызовов: каждый вызов получает в нём свою область для локальных переменных и освобождает её, когда метод завершается;
- куча — общая память для объектов: объект, созданный через
new, лежит там, пока на него ведёт хоть одна ссылка.
Когда ссылок на объект не остаётся, сборщик мусора — часть среды выполнения .NET — со временем освобождает его память.
Локальная переменная int хранит число прямо в стеке. Локальная переменная класса хранит в стеке только ссылку, а сам объект лежит в куче. Это и есть ссылка на облачный документ: документ один, а ссылок на него может быть сколько угодно.
В виджетах курса каждый вызов метода нарисован отдельной рамкой — это его область в стеке. Стрелка → у переменной ведёт к объекту в куче.
Где хранится значимый тип
Картинка со стеком и кучей упрощена: поле значимого типа лежит внутри своего объекта, то есть в куче, а в некоторых конструкциях языка компилятор переносит в кучу и локальные переменные.
На собеседовании часто спрашивают: «Значимые типы хранятся на стеке?» Точный ответ — значимый тип хранится там, где объявлена переменная:
- локальная переменная метода обычно лежит в стеке;
- поле объекта лежит вместе с объектом в куче;
- элемент массива
int[]лежит внутри массива, тоже в куче.
Значимый тип от ссылочного отличает то, что копирует присваивание, а не место в памяти.
Частая ошибка
Перед тем как сбросить счётчик, решили сохранить «резервную копию» через =:
Counter current = new Counter();
current.Value = 5;
Counter backup = current; // ✗
current.Value = 0;
Console.WriteLine(backup.Value); // 0Программа компилируется и печатает 0. Counter backup = current; не создаёт второй объект: backup — вторая ссылка на тот же самый. Сбросив current.Value, вы сбросили и «копию».
Чтобы сохранить старое значение, скопируйте само число: int saved = current.Value;. Тип int значимый, поэтому saved не зависит от объекта.
Главное
- Переменная значимого типа хранит само значение, и присваивание копирует его целиком.
- Переменная ссылочного типа хранит ссылку: присваивание копирует ссылку, и объект становится общим.
- Значимые типы — числа,
bool,charи типыstruct; ссылочные — массивы,stringи классы. - Стек и куча — упрощённая картина: значимый тип хранится там, где объявлена переменная.