Прошел курс. Получил код Dreamspark. Ввожу код на сайте, пишет: Срок действия этого кода проверки уже истек. Проверьте, правильно ли введен код. У вас осталось две попытки. Вы также можете выбрать другой способ проверки или предоставить соответствующие документы, подтверждающие ваш академический статус.
Как активировать код? |
Наследование в C#
Применение виртуальных функций
В поведении базовых ссылок в рассмотренных примерах усматривается один существенный недостаток. Когда базовая ссылка адресует многослойный объект, то именно тип, к которому она принадлежит, определяет ее способность адресоваться к членам соответствующего слоя этого объекта. И для того, чтобы получить доступ к членам расширенных слоев объекта, мы вынуждены приводить ссылку к типу соответствующего слоя, вручную изменяя ее полномочия (статус). Здесь именно тип ссылки определяет ее полномочия в адресации членов многослойного объекта.
А как бы сделать так, чтобы полномочия базовой ссылки автоматически устанавливались типом переданного родственного объекта. Есть такой механизм в объектно-ориентированных языках, который называется виртуальными функциями в наследовании. Его мы сейчас и рассмотрим применительно к C#.
Вернемся к нашему примеру. Посмотрим на члены в цепочке наследования классов Cylinder:Circle:Point, например на функцию Show(), которая есть в каждом слое. Для того, чтобы вызвать эту функцию из нужного слоя составного объекта с помощью адресующей этот объект ссылки, нам приходится объявлять ссылку, соответствующую типу этого слоя, присваивать ей адрес составного объекта и только потом через нее вызывать нужный член.
Есть более простой способ выполнить то же самое, если в базовом классе объявить функцию Show() виртуальной с помощью ключевого слова virtual, а в каждом классе-наследнике подтвердить новую версию этой функции с помощью ключевого слова override (переопределенная). После такого объявления компилятором будет создана таблица виртуальных функций. Она реализует механизм виртуальной адресации и позволит нам с помощью ссылки базового типа, которой будет присвоен адрес любого из объектов-родственников, вызывать переопределенные в этом объекте виртуальные функции. При этом явного (вручную) приведения ссылки базового типа к типу фактически адресуемого объекта не потребуется, среда исполнения сама извлечет из нужного слоя объекта подходящий член, определив его по типу самого объекта.
Одно важное условие нужно соблюдать при объявлении и переопределении виртуальных функций:
- Все виртуальные функции должны иметь одинаковые сигнатуры
- И еще одно условие, поскольку механизм вызова виртуальных функций связан с объектами-экземплярами, применять его к статическим функциям нельзя
Чаще всего, когда разрабатывается новый класс как расширение наследуемого (или цепочки наследуемых) класса, программист внутри этого расширения задействует весь потенциал наследуемых классов, вводя свои настройки и сервисы для вызывающего кода. При создании экземпляров этого класса в вызывающем коде программист охотнее использует только добавленное расширение, то есть добавленный слой составного объекта. Если объявить ссылку на базовый класс и присваивать ей объекты производных классов (без приведения типов), то ссылка всегда будет адресоваться к членам базового класса. Но для виртуальных членов порядок поиска по цепочке наследования будет начинаться всегда с расширения в сторону базового класса.
Для всей иерархии виртуальных функций автоматически создается и поддерживается таблица виртуальных функций, которая при создании объекта производного класса является присоединенной к нему и заполняется адресами всех переопределений виртуальных функций.
Если в иерархической цепочке наследования в каком-то одном или нескольких производных классах переопределение виртуальной функции пропущено, то для клиента производного класса будет вызвана та ее версия, которая является ближайшей по иерархическим меркам в направлении базового класса.
Объявим в нашем примере функцию Show() виртуальной в базовом классе, а все ее версии в производных классах переопределим. Упрощенный код примера будет таким
using System; namespace Test { class Point { string name = "Слой Point"; public void Show0() { Console.WriteLine(name); } public virtual void Show() { Console.WriteLine(name); } } class Circle : Point { string name = "Слой Circle"; new public void Show0() { Console.WriteLine(name); } public override void Show() { Console.WriteLine(name); } } class Cylinder : Circle { string name = "Слой Cylinder"; new public void Show0() { Console.WriteLine(name); } public override void Show() { Console.WriteLine(name); } } // Вызывающая сторона class MyClass { public MyClass() { // Создаем объекты для всех типов // иерархической цепочки наследования // и адресуем их базовой ссылкой Point[] point = { new Point(), new Circle(), new Cylinder() }; // Распечатываем Console.WriteLine("Адресация к невиртуальной функции"); for (int i = 0; i < point.Length; i++) { point[i].Show0(); } Console.WriteLine("\nАдресация к виртуальной функции"); for (int i = 0; i < point.Length; i++) point[i].Show(); Console.WriteLine("\nТо же самое, только вручную"); point[0].Show0(); ((Circle)point[1]).Show0(); ((Cylinder)point[2]).Show0(); } } // Запуск class Program { static void Main() { // Настройка консоли Console.Title = "Механизм виртуальных функций"; Console.ForegroundColor = ConsoleColor.White; Console.CursorVisible = false; Console.WindowWidth = 60; Console.WindowHeight = 10; new MyClass();// Чтобы сработал конструктор Console.ReadLine(); } } }Листинг 9.14 . Иллюстрация механизма виртуальных функций
Обратите внимание, что в каждом производном классе есть переменная с одинаковым именем name. Но ее помечать как новую не нужно, поскольку она невидима в производном классе. При адресации ссылкой базового типа к не виртуальным функциям родственных объектов всегда адресуется только базовый слой. При подобной адресации к виртуальным функциям адресуется член расширения самого объекта или ближайший в глубину наследования. Поиск адресуемого члена ищется послойно, начиная с адресуемого слоя в сторону базового.
Результат, выдаваемый программой, будет таким