Какие способы хранения значимых типов в C# существуют
Что проверяют
Понимаете ли вы, что «значимый тип живёт на стеке» это неверное упрощение.
Как отвечать
Значимый тип не имеет постоянного места жительства: он хранится там, где объявлен.
- на стеке: локальная переменная метода и параметр, если они не захвачены и не упакованы;
- в куче внутри объекта: поле класса. Структура целиком лежит в объекте-владельце, отдельного выделения нет;
- в куче в массиве: элементы
int[]илиPoint[]лежат подряд в одном блоке, без ссылок. Именно поэтому массивы структур эффективны и хорошо ложатся на кэш процессора; - в куче после упаковки: приведение к
objectили к интерфейсу создаёт объект-обёртку. Это выделение памяти и нагрузка на сборщик; - в куче внутри замыкания: захваченная лямбдой локальная переменная переезжает в сгенерированный компилятором класс;
- в куче внутри конечного автомата async-метода: локальные переменные асинхронного метода переживают
await, поэтому становятся полями класса состояния; - в статическом поле: в области загрузчика типов.
Отсюда практические выводы: главная выгода структур не в стеке, а в отсутствии отдельного объекта и подсчёта ссылок; главный риск это упаковка в неожиданных местах (сравнение через необобщённый IComparer, добавление в ArrayList, форматирование строки).
Полезно назвать ref struct (Span<T>), который компилятор обязывает оставаться на стеке: его нельзя упаковать, положить в поле класса или использовать в async-методе. Отсюда и его ограничения.
Где спотыкаются
Говорят «структуры на стеке, классы в куче» без оговорок.
Не замечают упаковку и удивляются нагрузке на сборщик в горячем цикле.
Делают большие изменяемые структуры и получают дорогое копирование.
Не знают, зачем Span<T> нельзя использовать в асинхронном методе.