Наследование и полиморфизм
Наследование, интерфейсы, virtual/override, перегрузка методов и почему в C# нет множественного наследования классов.
6 вопросов
JuniorТеорияОчень частоКак работает наследование классов в C# и сколько базовых классов допустимо?
Как работает наследование классов в C# и сколько базовых классов допустимо?
Наследование позволяет производному классу расширить один базовый класс записью class D : B, автоматически переиспользуя доступные члены базового и добавляя или уточняя свои. C# допускает только одиночное наследование классов — у класса не более одного прямого базового — хотя интерфейсов он может реализовать много. Каждый класс в итоге наследуется от System.Object.
Типичные ошибки
- ✗Думать, что C# допускает несколько базовых классов, как
class D : B1, B2 - ✗Считать, что унаследованные члены базового нужно заново объявлять в производном, чтобы пользоваться ими
- ✗Не знать, что каждый класс в итоге наследуется от
System.Object
Уточняющие вопросы
- →Как производный класс обращается к члену базового, который он перекрыл?
- →Если C# запрещает множественное наследование классов, как объединить несколько контрактов типа?
JuniorТеорияЧастоЧто такое интерфейс в C# и как класс связан с ним?
Что такое интерфейс в C# и как класс связан с ним?
interface — это контракт: набор сигнатур членов (методов, свойств, событий), которые тип обязуется предоставить, без состояния экземпляра. Класс заявляет, что реализует интерфейс, и должен предоставить каждый член; эти члены неявно public. Класс может реализовать много интерфейсов, а код может работать с любым реализатором через тип интерфейса.
Типичные ошибки
- ✗Думать, что интерфейс несёт реализацию или состояние экземпляра, как базовый класс
- ✗Считать, что класс может реализовать лишь один интерфейс
- ✗Помечать реализации членов интерфейса избыточным
publicили ошибочнымprivate
Уточняющие вопросы
- →Чем явная реализация интерфейса отличается от неявной и зачем она нужна?
- →Что добавляет метод интерфейса по умолчанию и чего он не меняет?
MiddleТеорияЧастоВ чём разница между перегрузкой и переопределением метода в C#?
В чём разница между перегрузкой и переопределением метода в C#?
Перегрузка определяет несколько методов с одним именем, но разными списками параметров в области видимости; компилятор выбирает один по типам аргументов на этапе КОМПИЛЯЦИИ. Переопределение заменяет базовый virtual-метод в производном классе методом с совпадающей сигнатурой, разрешаясь во ВРЕМЯ ВЫПОЛНЕНИЯ по фактическому типу объекта. Это несвязанные механизмы: один выбирает среди имён, другой подменяет виртуальную реализацию.
Типичные ошибки
- ✗Смешивать перегрузку (компиляция, по параметрам) с переопределением (рантайм, по типу)
- ✗Думать, что методы можно перегрузить только по типу возврата
- ✗Считать, что перегрузке нужны
virtual/overrideили иерархия классов
Уточняющие вопросы
- →Можно ли перегрузить два метода, отличающихся лишь
refпротивout? - →Почему разрешение перегрузки происходит до динамической диспетчеризации, когда применимы оба?
MiddleТеорияЧастоЧем virtual/override отличаются от new при переопределении метода в производном классе?
Чем virtual/override отличаются от new при переопределении метода в производном классе?
Пометка базового метода как virtual, а производного — override включает динамическую диспетчеризацию во время выполнения: вызов через ссылку базового типа выполнит производную версию, выбранную по фактическому типу объекта. new же скрывает член базового со связыванием во время компиляции — какой метод выполнится, зависит от статического типа ссылки, поэтому ссылка базового типа всё равно вызовет базовый метод.
Типичные ошибки
- ✗Считать, что
newдаёт полиморфизм, а не скрытие по статическому типу во время компиляции - ✗Забывать, что базовый метод должен быть
virtual, прежде чем производный сможет егоoverride - ✗Ожидать, что ссылка базового типа на скрытый через
newметод вызовет производную версию
Уточняющие вопросы
- →О чём предупреждает компилятор, если скрыть базовый метод без
new? - →Как
sealed overrideменяет возможности дальше-производного класса?
MiddleТеорияРедкоПоддерживает ли C# множественное наследование и как объединить несколько типов?
Поддерживает ли C# множественное наследование и как объединить несколько типов?
C# запрещает множественное наследование классов — у класса не более одного базового — чтобы избежать ромбовидной проблемы неоднозначных унаследованных реализаций. Зато класс может реализовать любое число интерфейсов. Поскольку интерфейсы объявляют контракты (и не требуют общего состояния реализации), именно так C# достигает множественного наследования ТИПА без неоднозначности множественного унаследованного кода.
Типичные ошибки
- ✗Думать, что C# допускает несколько базовых классов, если ромб решается самым левым
- ✗Считать, что класс может реализовать лишь один интерфейс
- ✗Не видеть в интерфейсах ответ C# на множественное наследование типа
Уточняющие вопросы
- →Если два реализуемых интерфейса объявляют одинаковый метод, как класс удовлетворяет оба?
- →Как методы интерфейса по умолчанию вновь вносят ограниченную ромбовидную проблему?
SeniorТеорияРедкоЧто делает sealed на классе против переопределённого метода в C#?
Что делает sealed на классе против переопределённого метода в C#?
sealed-класс вообще нельзя наследовать — он лист в иерархии. sealed override на методе сохраняет переопределение, но запрещает любому дальше-производному классу переопределить его снова, обрывая виртуальную цепочку. Запечатывание выражает намерение (этот тип/переопределение финальны) и позволяет JIT девиртуализовать вызовы на запечатанном типе, ведь переопределяющего подтипа быть не может.
Типичные ошибки
- ✗Думать, что
sealed-класс всё ещё можно наследовать или нельзя создать экземпляр - ✗Считать, что
sealed overrideубирает переопределение, а не запрещает дальнейшее - ✗Упускать, что запечатывание включает девиртуализацию вызовов в JIT
Уточняющие вопросы
- →В какой момент виртуальной цепочки можно впервые применить
sealed override? - →Как запечатывание класса позволяет JIT встроить ранее виртуальный вызов?