Наследование и полиморфизм
Наследование позволяет одному классу расширить другой: производный класс переиспользует доступные члены базового и добавляет или специализирует свои. Полиморфизм — вторая половина той же идеи: код, написанный против базового типа, во время выполнения вызывает поведение фактического производного объекта. Вместе они дают строить иерархии, где общее лежит в базовом классе, а различия — в производных.
В C# у этой модели есть жёсткие правила, вокруг которых и строятся вопросы собеседования. Класс наследует не более одного базового (одиночное наследование), но реализует сколько угодно интерфейсов. Метод участвует в полиморфизме, только если он virtual (или abstract) в базовом и override в производном; new вместо override — это не полиморфизм, а сокрытие члена по статическому типу ссылки. Перегрузка и переопределение — вообще разные механизмы: первую выбирает компилятор по параметрам, вторую — рантайм по типу объекта. Полная карта — в слоях ниже.
Карта темы
- Наследование —
class D : B, переиспользование членов, одиночное наследование и кореньSystem.Object. - Интерфейсы — контракт из сигнатур без состояния, неявно
publicчлены, много интерфейсов на класс. - Виртуальные методы —
virtual/abstractи динамическая диспетчеризация по фактическому типу объекта. - Переопределение методов —
overrideпротивnew: истинный полиморфизм против сокрытия по статическому типу. - Перегрузка методов — одно имя, разные параметры; выбор при компиляции, не путать с переопределением.
- Множественное наследование — один базовый класс, много интерфейсов и ромбовидная проблема.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
Считать, что C# допускает class D : B1, B2 | Не компилируется — множественное наследование классов запрещено; несколько контрактов комбинируют интерфейсами |
Думать, что new даёт полиморфизм | new лишь скрывает член по статическому типу; ссылка базового типа вызовет базовый метод, а не производный |
Забыть virtual/abstract на базовом методе | Производный override не скомпилируется — переопределять можно только виртуальный член |
| Путать перегрузку с переопределением | Перегрузка выбирается по параметрам при компиляции, переопределение — по фактическому типу объекта в рантайме |
| Пытаться перегрузить только по типу возврата | Не компилируется — тип возврата не входит в сигнатуру для разрешения перегрузки |
| Считать, что интерфейс несёт состояние или готовую реализацию | Интерфейс — это контракт из сигнатур; классу нужно предоставить тело каждого члена, и эти члены неявно public |
Ждать, что sealed-класс всё ещё можно наследовать | sealed-класс — лист иерархии; наследование запрещено, а JIT может девиртуализовать вызовы на нём |
Значение для собеседований
Наследование и полиморфизм спрашивают почти на любом собеседовании по C# — но проверяют модель диспетчеризации, а не синтаксис. Кандидат, который объясняет полиморфизм через «вызов через ссылку базового типа выбирает метод по фактическому типу объекта», сразу выделяется на фоне «ну, наследник переопределяет метод».
Что обычно проверяют:
- Одиночное наследование классов против множественной реализации интерфейсов, и почему так.
- Разницу
virtual/override(рантайм-диспетчеризация) иnew(сокрытие по статическому типу). - Перегрузку (компиляция, по параметрам) против переопределения (рантайм, по типу) — это несвязанные механизмы.
- Что интерфейс — это контракт без состояния, а его члены у класса неявно
public. - Ромбовидную проблему и почему C# решает её запретом множественного наследования классов.
Типичный неверный ответ: «new и override — это одно и то же, оба переопределяют метод». Это открывает разговор о том, что override включает динамическую диспетчеризацию, а new привязывается к статическому типу ссылки, — и одна и та же переменная базового типа даст разный результат в зависимости от того, каким словом переопределён метод.