Ответ
Нет, не всегда. Хотя это основное правило, существуют важные исключения, обусловленные оптимизациями компилятора и рантайма:
- Анализ побега (Escape Analysis): Современные JIT-компиляторы (например, в .NET 5+) могут определить, что объект ссылочного типа не "убегает" за пределы метода (т.е. на него не сохраняются ссылки вне стека этого метода). В таком случае объект может быть размещён на стеке для повышения производительности.
- Захват переменных в замыканиях: Локальные переменные, захваченные лямбда-выражением или анонимным методом, обычно "поднимаются" в сгенерированный класс и размещаются в куче. Однако, если время жизни переменной строго ограничено стеком, возможны оптимизации.
Пример с анализом побега (концептуальный):
void ProcessData()
{
// Компилятор может разместить этот StringBuilder на стеке,
// если анализ показывает, что он не покидает этот метод.
var sb = new StringBuilder();
sb.Append("Hello");
Console.WriteLine(sb.ToString());
// 'sb' здесь уничтожается, не оставляя ссылок в куче.
}
Практический вывод: Для разработчика правило "ссылочные типы — в куче" остаётся верной ментальной моделью. Эти оптимизации — "магия" под капотом, которая ускоряет код, но не меняет семантику языка. Полагаться на размещение в стеке в своём коде нельзя.