Что произойдет при передаче структуры в метод, принимающий интерфейс?
Что проверяют
Видите ли вы скрытую упаковку и понимаете ли, что метод получит копию. Вопрос короткий, но по нему сразу понятно, различает ли кандидат значимые и ссылочные типы на уровне памяти, а не определений.
Как отвечать
Параметр интерфейсного типа это ссылка, а структура значимый тип. Поэтому при передаче происходит упаковка: в куче создаётся объект, туда копируются поля структуры, и в метод уходит ссылка на эту копию. Следствий три:
- аллокация и нагрузка на сборщик мусора на каждом вызове, в горячем цикле это заметная стоимость;
- метод работает с копией, поэтому изменение полей через интерфейс не видно в исходной переменной;
- вызов идёт виртуально, через таблицу методов упакованного объекта, так что встраивания не будет.
Как избежать: обобщённый метод с ограничением, void Do<T>(T value) where T : IShape. Для значимого типа генерируется отдельная версия кода, упаковки нет, вызов прямой. Дополнительно помогает readonly struct, иначе компилятор вставляет защитные копии при обращении к структуре через доступное только для чтения поле или параметр in.
Отдельно стоит сказать, что явная реализация интерфейса у структуры вызывается только через интерфейс, то есть упаковку навязывает сама форма объявления.
Где спотыкаются
Говорят про распаковку при каждом обращении. Её нет: распаковка нужна только для явного обратного приведения к типу структуры. Вызов метода интерфейса выполняется прямо на упакованном экземпляре, а лишние расходы дают аллокация и виртуальный вызов.
Ловят классику с изменяемой структурой: вызвали метод, меняющий поле, через переменную интерфейсного типа и уверены, что поправили объект. Поправили копию в куче. Ровно по этой причине изменяемых структур избегают, а foreach по коллекции структур отдаёт копию элемента.
Обобщают вывод до «обобщения тоже упаковывают». С ограничением по интерфейсу упаковки нет, а вот перечисление коллекции, отданной как IEnumerable<T>, вернёт упаковку структурного перечислителя. Именно поэтому у списков в горячем коде важно, каким типом объявлена переменная.