Введение в ООП с примерами на C#. Часть третья. Практические аспекты использования полиморфизма
17К открытий18К показов
Рассказывает 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К открытий18К показов



