Введение в ООП с примерами на C#. Часть третья. Практические аспекты использования полиморфизма
17К открытий17К показов
Рассказывает Akhil Mittal
Введение
Раньше в этой серии мы говорили о полиморфизме и наследовании.
В этой статье мы опять будем говорить о полиморфизме, но в этот раз сосредоточимся именно на практических нюансах, а не на теории. Если вы овладеете технологией, описанной в этой статье, то считайте, что изучили 50% ООП.
Ключевые слова New и Override в C#
Для начала создадим новое консольное приложение и два класса в нём:
Мы видим, что эти классы содержат три метода с попарно одинаковыми именами. Теперь выполним следующий код из Program.cs
:
Жмём F5, т.е. выполняем код, и что мы видим?
Но кроме вывода, мы получили ещё и три предупреждения от компилятора:
Что нужно запомнитьи>: мы можем записать в переменную класса-родителя объект наследника, но не наоборот.
ClassA
— родитель ClassB
. То есть ClassB
содержит то, что находится в ClassA
и ещё что-то своё. В этом причина правила, которое мы записали выше: класс-родитель не содержит описания всех необходимых полей и методов класса-наследника, поэтому мы не можем использовать ClassA
как ClassB
.
Теперь посмотрим, что у нас происходит в коде. С x
и y
всё понятно: они объявлены и инициализированны одним и тем же типом. Рассмотрим подробнее z
. Эта переменная типа ClassB
, а её значение — объект типа ClassA
, хотя в данном контексте нет никакой разницы, какого типа её значение, вывод всегда будет аналогичен выводу от y
. Выбор метода по типу ссылки, а не по типу объекта — это стандартное поведение, когда явно не указан приоритет методов, о чём свидетельствуют warning’и. Как же описать требуемое поведение? Здесь нам как раз помогут ключевые слова new
и override
.
Давайте проведём эксперимент
Добавим к двум методам из ClassB
ключевые слова new
и override
следующим образом:
Если мы сейчас выполним Program.cs
, то на выходе получим:
Ошибка возникает из-за того, что поля родителя не помечены ключевым словом virtual
. Этот модификатор обозначает, что мы имеем право вызывать метод из дочернего класса или перезаписывать его. Добавим virtual
ко всем методам ClassA
:
И снова запустим Program.cs
:
Очевидно, что метод дочернего класса вызвался только там, где стоял модификатор override
. В свзяи с чем делаем вывод: override
значит, что помеченный метод — новая версия родительского и должен использоваться вместо него. И, напротив, new
обозначает, что метод, хоть и случайно имеет такое же имя, является по сути абсолютно другим, а значит, в нашем примере должен выполняться метод родительского класса. Если мы не пишем никакого модификатора, мы подразумеваем именно new
.
Разберём подробнее логику C#. Когда вызывается метод какого-то объекта по ссылке, то в первую очередь он смотрит на тип ссылки. Если в этом классе обнаружен модификатор virtual
, он начинает искать среди дочерних классов тип объекта, и, если встречает new
, запускает последний override
метод, который встретил (либо метод типа ссылки). Возможно, это не слишком понятно, обратимся к более сложному примеру.
Эксперимент с тремя классами
Результатом такого эксперимента станет:
В первом случае мы имеем дело с типом ссылки ClassA
и типом объекта ClassB
. Компилятор действует вполне очевидно:
Во втором случае тип объекта у нас уже ClassC
. Поскольку он наследуется от ClassA
не напрямую, а через ClassB
, наша диаграмма будет уже несколько сложнее.
В третьем случае мы имеем дело снова с двумя классами, ClassA
мы просто игнорируем. Если учесть это, то вывод будет очевиден, но всё же вот схема:
Важным замечанием будет, что следующий код:
Выдаст ошибку:
Ведь из-за того, что в B
метод помечен как new
, он не наследует свойство virtual
, а значит не может быть перезаписан с помощью override
в C
. Правильным вариантом было бы добавить к описанию метода в B
ключевое слово virtual
или изменить в C
override
на new
, в зависимости от требуемого поведения.
Ключевое слово base
Теперь, когда мы разобрались с самой сложной частью, стоит напомнить про возможность вызова методов родительского класса из дочернего. Простой пример:
Даст нам вывод:
В первом случае выполняется метод ClassB
, который через base
вызывает метод из ClassA
. Во втором — XXX()
из ClassC
, который обращается к ClassB
, а тот, в свою очередь, к ClassA
.
Немного рекурсии
В этом примере вызов ClassB.XXX()
всегда будет приводить к созданию нового объекта типа ClassB
в ссылке ClassA
. Очевидно, что по такой ссылке снова будет вызван ClassB.XXX()
и т.д. В данном случае выводом будет ошибка:
В заключение
Подведём итоги:
- В C# мы можем записать в переменную класса-родителя объект наследника, но не наоборот;
- Модификатор
override
используется, чтобы указать на то, что должен вызваться метод именно дочернего класса; - Чтобы использовать модификаторы
override
иnew
, метод родительского класса должен быть помечен ключевым словомvirtual
; - Когда вызывается метод какого-то объекта по ссылке, то С# в первую очередь смотрит на тип ссылки. Если в этом классе обнаружен модификатор
virtual
, он начинает искать среди дочерних классов тип объекта, и, если встречаетnew
, запускает последнийoverride
метод, который встретил (либо метод типа ссылки).
17К открытий17К показов