Как работает полиморфизм в C#
Что проверяют
Понимаете ли вы механизм, а не определение из учебника: как рантайм выбирает метод и чем override отличается от new. Следом обычно дают код с Base b = new Derived() и спрашивают, что выведется.
Как отвечать
В C# полиморфизм бывает трёх видов:
- подтипов (динамический): метод помечен
virtualилиabstractв базовом классе иoverrideв наследнике, либо объявлен в интерфейсе. Вызов идёт через таблицу методов объекта, поэтому выполняется версия фактического типа, а не типа переменной; - ad hoc (статический): перегрузка методов и операторов. Выбор делает компилятор по статическим типам аргументов: при
object x = "text"вызовPrint(x)выберетPrint(object), а неPrint(string). Отложить выбор до рантайма можно черезdynamic; - параметрический: дженерики. Для ссылочных типов JIT использует общий код, для значимых генерирует отдельную специализацию, поэтому
List<int>работает без упаковки.
Дальше то, что показывает уровень:
newвместоoverride: скрывает метод, а не переопределяет. Через ссылку типаBaseвызовется базовая версия;sealed override: запрещает дальнейшее переопределение и даёт JIT шанс девиртуализировать вызов;- интерфейсы: с C# 8 в них бывают реализации по умолчанию, с C# 11 статические абстрактные члены, на которых построена generic math:
T.Zeroи операторы приwhere T : INumber<T>; - альтернатива:
switchс сопоставлением по типам. Удобно для закрытого набора вариантов, но каждый новый тип требует правки всех таких мест.
Где спотыкаются
Называют полиморфизмом только переопределение и забывают про перегрузку и дженерики, хотя интервьюер ждёт как раз разницу между выбором при компиляции и в рантайме.
Путают new и override и ошибаются в задаче «что выведется», когда у наследника метод объявлен без override и компилятор выдал лишь предупреждение CS0114.
Вызывают виртуальный метод из конструктора базового класса. Выполнится переопределение наследника, но тело конструктора наследника ещё не отработало, и поля, которые заполняются там, пока пустые.