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

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

    Теперь пришел черед выяснить, чем замещение методов отличается от переопределения виртуальных методов. Для большей наглядности сразу рассмотрим пример. Он будет несложным, но показательным. Идея, положенная в основу примера, простая. Имеется базовый класс Alpha, в котором есть два метода: hi() и hello(). Метод hi() виртуальный, а метод hello() обычный (не виртуальный). А еще в классе Alpha есть метод show(). Это обычный (не виртуальный) метод. При его вызове последовательно вызываются методы hello() и hi(). Далее, на основе класса Alpha создается производный класс Bravo. В классе Bravo замещается метод hello() и переопределяется метод hi(). При этом метод show() просто наследуется. Таким образом, в классе Bravo есть унаследованный метод show(), при вызове которого вызываются методы hello() и hi(). Интрига связана с тем, в каких ситуациях какие версии этих методов будут вызываться. А теперь рассмотрим программный код, представленный ниже.

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

namespace pr146_1
{
    // Базовый класс: 
    class Alpha {
        // Обычный (не виртуальный) метод: 
        public void hello() {
            Console.WriteLine("Метод hello() из класса Alpha");
        }
        // Виртуальный метод: 
        public virtual void hi() {
            Console.WriteLine("Метод hi() из класса Alpha");
        }
        // Обычный (не виртуальный) метод: 
        public void show() {
            // Вызов методов: 
            hello();
            hi();
            Console.WriteLine();
        }
    }

    // Производный класс: 
    class Bravo: Alpha{
        // Замещение метода: 
        new public void hello() {
            Console.WriteLine("Метод hello() из класса Bravo");
        }
        // Переопределение виртуального метода: 
        public override void hi() {
            Console.WriteLine("Метод hi() из класса Bravo");
        }
    }

    // Класс с главным методом: 
    class Program
    {
        // Главный метод:
        static void Main()
        {
            Bravo A = new Bravo();
            Console.WriteLine("Выполнение команды A.show():");
            // Вызов метода:
            A.show();
            // Создание объекта производного класса:
            Bravo B = new Bravo();
            Console.WriteLine("Выполнение команды B.hello():");
            // Вызов замещающего метода из производного класса:
            B.hello();
            Console.WriteLine("Выполнение команды В.hi():");
            // Вызов переопределенного метода:
            B.hi();
            Console.WriteLine("Выполнение команды В.show():");
            // Вызов унаследованного метода:
            B.show();
            Console.WriteLine("После выполнения команды А=В");
            // Объектной переменной базового класса присваивается 
            // ссылка на объект производного класса:
            A = B;
            Console.WriteLine("Выполнение команды A.hello():");
            // Вызов замещаемого метода:
            A.hello();
            Console.WriteLine("Выполнение команды A.hi():");
            // Вызов переопределенного метода:
            A.hi();
            Console.WriteLine("Выполнение команды A.show():");
            // Вызов унаследованного метода:
            A.show();
            // Задержка:
            Console.ReadLine();
        }
    }
}
Архив проекта можно взять здесь.

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


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

    В главном методе сначала создается объект класса Alpha, и ссылка на него записывается в объектную переменную А того же класса. Результат вызова метода show() через эту переменную вполне ожидаем: вызываются те версии методов hello() и hi(), которые описаны в классе Alpha.

    На следующем этапе создается объект класса Bravo и ссылка на объект записывается в объектную переменную В класса Bravo. При выполнении команд

  B.hello();
и
  B.hi();
убеждаемся, что вызываются версии методов hello() и hi(), описанные непосредственно в классе Bravo. Сюрприз получаем при выполнении команды
  B.show();     . 
Оказывается, что в этом случае для метода hi() вызывается версия из класса Bravo, а для метода hello() вызывается версия из класса Alpha.


Метод show() вызывается из объекта класса Bravo, но сам метод описан в классе Alpha. Версия виртуального переопределенного метода hi() определяется в соответствии с объектом, из которого вызывается метод show(). Версия замещаемого в производном классе метода hello() определяется по классу, в котором описан вызывающийся метод show().

    После выполнения команды

  A = B;
обе переменные А и В ссылаются на один и тот же объект класса Bravo. В частности, объектная переменная А базового класса Alpha ссылается на объект производного класса Bravo. Через эту переменную мы последовательно вызываем все три метода (команды
  A.hello();     ,
  A.hi();
и
  A.show();
). Вывод очевидный: версия виртуального переопределенного метода определяется по объекту, из которого вызывается метод, а версия замещаемого метода определяется типом объектной переменной.


Переопределяется только виртуальный метод. Механизм замещения формально можно применять и к виртуальным методам. Но это все равно будет замещение, а не переопределение метода. Версия метода определяется по объекту, из которого вызывается метод, если метод переопределен.

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




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