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

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

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

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


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

    Теперь представим себе, что в базовом классе несколько конструкторов и среди них нет конструктора без аргументов. Сразу возникает два вопроса:

    Решение проблемы состоит в особом способе описания конструктора производного класса, а именно в описании конструктора производного класса после закрывающей круглой скобки через двоеточие указывается ключевое слово base. После него в круглых скобках указывают аргументы, передаваемые конструктору базового класса. Если аргументы конструктору базового класса передавать не нужно, то скобки оставляют пустыми. В теле конструктора производного класса размещают команды, выполняемые после выполнения команд из конструктора базового класса. Шаблон описания конструктора производного класса выглядит так:

public Конструктор (аргументы): base(аргументы) {
  // Команды конструктора производного класса
}

    В примере ниже представлена программа, в которой в базовом и производном классе описываются конструкторы.

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

namespace pr141_1
{
    // Базовый класс: 
    class Alpha {
        // Целочисленное поле: 
        public int code;
        // Конструктор с одним аргументом: 
        public Alpha(int n) {
            code = n;
            Console.WriteLine("Alpha (один аргумент): {0}", code);
        }
        // Конструктор без аргументов: 
        public Alpha() { 
            code = 123;
            Console.WriteLine("Alpha (без аргументов): {0}", code); 
        }
    }

    // Производный класс: 
    class Bravo: Alpha {
        // Символьное поле: 
        public char symb;
        // Конструктор с двумя аргументами: 
        public Bravo(int n, char s): base(n){ 
            symb = s;
            Console.WriteLine("Bravo (два аргумента): {0} и \'{1}\'", code, symb);
        }
        // Конструктор с целочисленным аргументом: 
        public Bravo(int n): base(n){ 
            symb = 'A';
            Console.WriteLine("Bravo (int-аргумент): {0} и \'{1}\'", code, symb);
        }
        // Конструктор с символьным аргументом: 
        public Bravo(char s): base (321){ 
            symb = s;
            Console.WriteLine("Bravo (char-аргумент): {0} и \'{1}\'", code, symb);
        }
        // Конструктор без аргументов: 
        public Bravo(): base(){ 
            symb = '0';
            Console.WriteLine("Bravo (без аргументов): {0} и \'{1}\'", code, symb);
        }
    }

    // Класс с главным методом:
    class Program
    {
        // Главный метод:
        static void Main()
        {
            // Создание объекта базового класса
            // (конструктор без аргументов):
            Alpha A1 = new Alpha();
            Console.WriteLine();
            // Создание объекта базового класса 
            // (конструктор с одним аргументом):
            Alpha A2 = new Alpha(100);
            Console.WriteLine();
            // Создание объекта производного класса 
            // (конструктор с двумя аргументами):
            Bravo B1 = new Bravo(200, 'В');
            Console.WriteLine();
            // Создание объекта производного класса 
            // (конструктор с целочисленным аргументом):
            Bravo B2 = new Bravo(300);
            Console.WriteLine();
            // Создание объекта производного класса 
            // (конструктор с символьным аргументом):
            Bravo B3 = new Bravo('C');
            Console.WriteLine();
            // Создание объекта производного класса 
            // (конструктор без аргументов):
            Bravo B4 = new Bravo();
            // Задержка:
            Console.ReadLine();
        }
    }
}
Архив проекта можно взять здесь.

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


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

    В базовом классе Alpha есть целочисленное поле code и две версии конструктора: с целочисленным аргументом и без аргументов. В каждой версии конструктора полю code присваивается значение, после чего в консольное окно выводится сообщение с названием класса Alpha, информацией о количестве аргументов конструктора и значением поля code. Это сделано для того, чтобы по сообщениям в консольном окне можно было проследить порядок вызова конструкторов. Пример создания объектов базового класса с использованием конструктора без аргументов и с одним аргументом есть в главном методе программы.

    В производном классе Bravo, кроме наследуемого поля code, описано еще и символьное поле symb. Версий конструкторов в классе Bravo четыре:

    В каждой из этих версий конструктора вызывается одна из версий конструктора базового класса. Например, в версии конструктора с двумя аргументами первый аргумент передается конструктору базового класса, а второй аргумент определяет значение символьного поля. Если конструктору производного класса передается один целочисленный аргумент, то он будет передан конструктору базового класса, а символьное поле получит значение 'А'. Вызов конструктора производного класса с символьным аргументом приведет к тому, что конструктор базового класса будут вызван с аргументом 321, а символьный аргумент конструктора производного класса при этом определяет значение символьного поля. Наконец, в конструкторе производного класса без аргументов вызывается конструктор базового класса без аргументов. В теле каждой версии конструктора для производного класса есть команда, которой выводится сообщение с названием класса Bravo, количеством аргументов конструктора и значениями полей.

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

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




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