Как происходит инициализация ссылочных и значимых типов в C#
Что проверяют
Понимаете ли вы, что происходит в памяти между new и первой строкой конструктора и где на самом деле живёт значимый тип. Следом почти всегда спрашивают, правда ли, что структуры хранятся в стеке, и в каком порядке выполняются инициализаторы полей и конструкторы при наследовании.
Как отвечать
Начните с разницы в семантике, а не в памяти: переменная значимого типа содержит сами данные, переменная ссылочного типа содержит ссылку на объект. Отсюда копирование при присваивании и передаче в метод.
- значимые типы (
struct,enum,int,bool,decimal): значение по умолчанию это все поля в нулях, его даётdefault(T). Поля класса и элементы массива обнуляет среда выполнения, а локальную переменную компилятор не даст прочитать до присваивания (ошибка CS0165); - ссылочные типы (
class,record, массивы, делегаты, строки):newвыделяет в управляемой куче уже обнулённую память, затем выполняются инициализаторы полей и конструкторы. Значение ссылки по умолчаниюnull; - порядок при наследовании: инициализаторы полей производного класса, затем конструктор базового со своими инициализаторами, затем тело конструктора производного. Инициализатор объекта
new Foo { X = 1 }отрабатывает уже после конструктора; - структуры в современных версиях: с C# 10 у структуры можно объявить конструктор без параметров, но
default(S)иnew S[10]его не вызывают. С C# 11 компилятор сам обнуляет поля, которые конструктор не присвоил.
Про память скажите аккуратно: значимый тип лежит там, где объявлен. Локальная переменная обычно в стеке или регистре, поле объекта и упакованное значение (object o = 5) в куче.
Где спотыкаются
Говорят «структуры всегда в стеке». Поле int внутри класса живёт в куче вместе с объектом, а локальная переменная, захваченная лямбдой или async-методом, переезжает в сгенерированный класс.
Путают nullable reference types с реальной защитой. Аннотация string? работает только на этапе компиляции, в рантайме ссылка всё равно может быть null.
Считают, что default(MyStruct) вызовет написанный конструктор без параметров. Не вызовет: поля будут обнулены, и инварианты конструктора нарушатся.