Могут ли объекты структур (value-типы) лежать в куче?

Ответ

Да, структуры (value-типы) часто оказываются в управляемой куче. По умолчанию они размещаются в стеке, но есть несколько распространённых сценариев, приводящих к аллокации в куче:

1. Упаковка (Boxing): При приведении структуры к ссылочному типу (object, ValueType, Enum или интерфейсу).

struct Point { public int X, Y; }
Point p = new Point { X = 5, Y = 10 };
object boxedPoint = p; // УПАКОВКА: 'p' копируется в кучу.
Point unboxedPoint = (Point)boxedPoint; // РАСПАКОВКА: копирование обратно.

2. Структура как поле ссылочного типа:

class Widget // Класс размещается в куче
{
    public Point Location; // Поле-структура будет частью объекта Widget в куче.
}

3. Элемент массива структур:

Point[] points = new Point[100]; // Сам массив (ссылочный тип) в куче.
// Все 100 элементов Point также хранятся в непрерывном блоке памяти внутри массива в куче.

4. Захват в замыкании или лямбда-выражении:

int counter = 0; // Локальная переменная-значение.
Action action = () => Console.WriteLine(counter++); // 'counter' захватывается.
// Компилятор создаёт класс для замыкания, и 'counter' становится его полем (куча).

5. async метод: Локальные переменные в async-методах становятся полями сгенерированного класса состояния, размещаемого в куче.

Практический вывод: Нельзя считать, что структуры всегда в стеке. Их размещение зависит от контекста. Избегайте неявной упаковки в критичных к производительности участках кода.

Ответ 18+ 🔞

Ну вот, опять про эти структуры и кучу. Слушай, как же жестоко жизнь устроена: вроде объявил ты себе struct — и думаешь, что он такой весь легковесный, в стеке живёт, быстрый. Ан нет, сука, жизнь оказывается сложнее.

Смотри, есть несколько классических способов, как твоя милая, простая структура может неожиданно оказаться в этой самой управляемой куче, где все объекты толпятся, как селёдки в бочке.

1. Упаковка (Boxing) — главный подвох.
Это когда ты свою структуру пытаешься запихнуть туда, где ждут ссылочный тип. Например, в object или в какой-нибудь интерфейс. CLR, блядь, видит это и говорит: «О, так тебе ссылочный тип нужен? Хорошо, держи!» — и создаёт на куче копию твоей структуры, заворачивает её в обёртку и отдаёт тебе ссылку на эту обёртку.

struct Point { public int X, Y; }
Point p = new Point { X = 5, Y = 10 };
object boxedPoint = p; // Всё, приехали. 'p' скопировали в кучу. Упаковали, блядь.
Point unboxedPoint = (Point)boxedPoint; // А тут распаковка — обратно копируем из кучи.

Это, кстати, овердохуища накладных расходов создаёт, если делать в цикле. Просто пиздец.

2. Структура как поле в классе.
Если твоя структура — поле какого-нибудь класса, то она живёт там же, где и сам класс. А класс-то в куче, ёпта. Всё просто.

class Widget // Класс — значит, в куче.
{
    public Point Location; // И эта структура тоже будет сидеть в куче, как часть объекта Widget.
}

3. Массив структур.
Тут тоже интересно. Сам массив — ссылочный тип, он в куче. И все его элементы, даже если это структуры, хранятся в одном непрерывном блоке памяти внутри этого массива. То есть тоже в куче, блядь.

Point[] points = new Point[100]; // Массив в куче, и все 100 Point'ов — тоже в куче, рядышком.

4. Захват в замыкании или лямбде.
Вот это вообще хитрая жопа. Берёшь локальную переменную-структуру, используешь её в лямбде — и компилятор тебе под капотом генерирует класс, куда это поле запихивает. А класс, как мы помним, в куче.

int counter = 0; // Вроде локальная переменная, значение.
Action action = () => Console.WriteLine(counter++); // Ага, щас. 'counter' захватили.
// Компилятор создаёт класс для замыкания, и 'counter' становится его полем. Всё, welcome to куча.

5. async методы.
Тут вообще мрак. Локальные переменные в async-методах превращаются в поля специального сгенерированного класса, который хранит состояние метода. И этот класс, естественно, живёт в куче. Так что если у тебя в async-методе есть структура — она там не в стеке, а в куче, как миленькая.

Практический вывод, чувак:
Не верь слепо, что структуры всегда в стеке. Это хуйня. Их размещение зависит от контекста, и если ты в перформанс-критичном коде начнёшь невольно упаковывать структуры — получишь такие тормоза, что мало не покажется. Следи за этим, блядь.