Шаг 145.
Язык программирования C#. Начала
Наследование. Переопределение виртуальных методов

    На этом шаге мы рассмотрим использование такого переопределения.

    Выше мы узнали, что в производном классе можно описать метод с таким же названием (и такими же аргументами и типом результата), что и у наследуемого из базового класса метода. В итоге получается, что описанный в производном классе метод "перекрывает", или замещает, метод, унаследованный из базового класса. Но есть еще один механизм, который внешне напоминает замещение метода, но в действительности имеет намного больший потенциал и который достаточно часто используется на практике. Речь идет о переопределении виртуальных методов. Сначала мы познакомимся с этим механизмом и научимся его использовать, а затем (в отдельном шаге) сравним переопределение виртуальных методов с замещением методов.

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

    Переопределение метода подразумевает два этапа. Это собственно переопределение, когда в производном классе описывается новая версия метода. Причем такая версия описывается с ключевым словом override. Но кроме этого, в базовом классе данный метод должен быть объявлен как виртуальный. Для этого в описании метода в базовом классе используют ключевое слово virtual.

    Пример, иллюстрирующий, как выполняется переопределение виртуальных методов, представлен в примере ниже.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace pr145_1
{
    // Базовый класс: 
    class Alpha {
        // Открытое поле: 
        public int alpha;
        // Виртуальный метод: 
        public virtual void show() {
            Console.WriteLine("Класс Alpha: {0}", alpha);
        }
        // Конструктор с одним аргументом: 
        public Alpha(int a){ 
            alpha = a;
        }
    }

    // Производный класс от класса Alpha: 
    class Bravo: Alpha {
        // Открытое поле: 
        public int bravo;
        // Переопределение виртуального метода: 
        public override void show(){
            Console.WriteLine("Класс Bravo: {0} и {1}", alpha, bravo);
        }
        // Конструктор с двумя аргументами:
        public Bravo(int a, int b): base(a){ 
            bravo = b;
        }
    }

    // Производный класс от класса Bravo: 
    class Charlie: Bravo {
        // Открытое поле: 
        public int charlie;
        // Переопределение виртуального метода: 
        public override void show(){
            Console.WriteLine("Класс Charlie: {0}, {1} и {2}", alpha, bravo, charlie);
        }
        // Конструктор с тремя аргументами: 
        public Charlie(int a, int b, int c): base(a, b) {
            charlie = c;
        }
    }

    // Класс с главным методом:
    class Program
    {
        static void Main()
        {
            // Главный метод:
            // Создание объекта класса Alpha:
            Alpha objA = new Alpha(10);
            // Проверка значения поля: 
            objA.show();
            Console.WriteLine();
            // Создание объекта класса Bravo:
            Bravo objB = new Bravo(20, 30);
            // Объектной переменной базового класса присваивается 
            // ссылка на объект производного класса: 
            objA = objB;
            // Проверка значений полей: 
            objA.show();
            objB.show();
            Console.WriteLine();
            // Создание объекта класса Charlie:
            Charlie objC = new Charlie(40, 50, 60);
            // Объектной переменной базового класса присваивается 
            // ссылка на объект производного класса: 
            objA = objC; 
            objB = objC;
            // Проверка значений полей: 
            objA.show(); 
            objB.show();
            objC.show();
            // Задержка:
            Console.ReadLine();
        }
    }
}
Архив проекта можно взять здесь.

    Ниже представлен результат выполнения программы.


Рис.1. Результат выполнения программы

    В этой программе мы описали три однотипных класса: Alpha, Bravo и Charlie. Класс Alpha является базовым для класса Bravo, а класс Bravo является базовым для класса Charlie. В классе Alpha есть целочисленное поле и конструктор с одним аргументом. В классе Bravo есть два целочисленных поля и конструктор с двумя аргументами. В классе Charlie есть три целочисленных поля и конструктор с тремя аргументами. Но главный наш интерес связан с методом show(), который описан в базовом классе Alpha и наследуется и переопределяется в классах Bravo и Charlie.

    Метод show() описан как виртуальный (с ключевым словом virtual). При вызове метода в консольном окне отображается значение целочисленного поля. Метод переопределяется в классе Bravo. Метод в классе Bravo описан с ключевым словом override. Метод описан так, что при вызове в консольном окне отображается значение двух целочисленных полей. Версия метода в классе Charlie также описана с ключевым словом override, а при вызове метода в консольном окне отображается значение трех целочисленных полей.

    В главном методе программы сначала создается объект objA класса Alpha. При вызове метода show() через переменную objA вызывается версия метода из класса Alpha. Далее создается объект objB класса Bravo. Причем переменной objA также присваивается ссылка на этот объект (так можно сделать, поскольку класс Alpha является базовым для класса Bravo). Метод show() вызывается через переменные objA и objB (которые ссылаются на один и тот же объект). В обоих случаях вызывается версия метода show(), описанная в классе Bravo. Наконец, создается объект objC класса Charlie и переменным objA и objB значением присваивается ссылка на этот объект. Через каждую из трех переменных вызывается метод show(). Во всех трех случаях вызывается та версия метода, что описана в классе Charlie.

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


Атрибут virtual наследуется. Это означает, что если метод в базовом классе объявлен как виртуальный, то он останется виртуальным при наследовании и переопределении.

    На следующем шаге мы рассмотрим переопределение и замещение методов.




Предыдущий шаг Содержание Следующий шаг