Опубликован: 25.03.2010 | Доступ: свободный | Студентов: 1447 / 158 | Оценка: 4.31 / 4.00 | Длительность: 25:42:00
Лекция 9:

Наследование в C#

Применение виртуальных функций

В поведении базовых ссылок в рассмотренных примерах усматривается один существенный недостаток. Когда базовая ссылка адресует многослойный объект, то именно тип, к которому она принадлежит, определяет ее способность адресоваться к членам соответствующего слоя этого объекта. И для того, чтобы получить доступ к членам расширенных слоев объекта, мы вынуждены приводить ссылку к типу соответствующего слоя, вручную изменяя ее полномочия (статус). Здесь именно тип ссылки определяет ее полномочия в адресации членов многослойного объекта.

А как бы сделать так, чтобы полномочия базовой ссылки автоматически устанавливались типом переданного родственного объекта. Есть такой механизм в объектно-ориентированных языках, который называется виртуальными функциями в наследовании. Его мы сейчас и рассмотрим применительно к C#.

Вернемся к нашему примеру. Посмотрим на члены в цепочке наследования классов Cylinder:Circle:Point, например на функцию Show(), которая есть в каждом слое. Для того, чтобы вызвать эту функцию из нужного слоя составного объекта с помощью адресующей этот объект ссылки, нам приходится объявлять ссылку, соответствующую типу этого слоя, присваивать ей адрес составного объекта и только потом через нее вызывать нужный член.

Есть более простой способ выполнить то же самое, если в базовом классе объявить функцию Show() виртуальной с помощью ключевого слова virtual, а в каждом классе-наследнике подтвердить новую версию этой функции с помощью ключевого слова override (переопределенная). После такого объявления компилятором будет создана таблица виртуальных функций. Она реализует механизм виртуальной адресации и позволит нам с помощью ссылки базового типа, которой будет присвоен адрес любого из объектов-родственников, вызывать переопределенные в этом объекте виртуальные функции. При этом явного (вручную) приведения ссылки базового типа к типу фактически адресуемого объекта не потребуется, среда исполнения сама извлечет из нужного слоя объекта подходящий член, определив его по типу самого объекта.

Одно важное условие нужно соблюдать при объявлении и переопределении виртуальных функций:

  1. Все виртуальные функции должны иметь одинаковые сигнатуры
  2. И еще одно условие, поскольку механизм вызова виртуальных функций связан с объектами-экземплярами, применять его к статическим функциям нельзя

Чаще всего, когда разрабатывается новый класс как расширение наследуемого (или цепочки наследуемых) класса, программист внутри этого расширения задействует весь потенциал наследуемых классов, вводя свои настройки и сервисы для вызывающего кода. При создании экземпляров этого класса в вызывающем коде программист охотнее использует только добавленное расширение, то есть добавленный слой составного объекта. Если объявить ссылку на базовый класс и присваивать ей объекты производных классов (без приведения типов), то ссылка всегда будет адресоваться к членам базового класса. Но для виртуальных членов порядок поиска по цепочке наследования будет начинаться всегда с расширения в сторону базового класса.

Для всей иерархии виртуальных функций автоматически создается и поддерживается таблица виртуальных функций, которая при создании объекта производного класса является присоединенной к нему и заполняется адресами всех переопределений виртуальных функций.

Если в иерархической цепочке наследования в каком-то одном или нескольких производных классах переопределение виртуальной функции пропущено, то для клиента производного класса будет вызвана та ее версия, которая является ближайшей по иерархическим меркам в направлении базового класса.

Объявим в нашем примере функцию 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. Но ее помечать как новую не нужно, поскольку она невидима в производном классе. При адресации ссылкой базового типа к не виртуальным функциям родственных объектов всегда адресуется только базовый слой. При подобной адресации к виртуальным функциям адресуется член расширения самого объекта или ближайший в глубину наследования. Поиск адресуемого члена ищется послойно, начиная с адресуемого слоя в сторону базового.

Результат, выдаваемый программой, будет таким


Максим Филатов
Максим Филатов

Прошел курс. Получил код Dreamspark. Ввожу код на сайте, пишет:

Срок действия этого кода проверки уже истек. Проверьте, правильно ли введен код. У вас осталось две попытки. Вы также можете выбрать другой способ проверки или предоставить соответствующие документы, подтверждающие ваш академический статус.

 

Как активировать код?