На этом шаге мы рассмотрим особенности вызова конструкторов при наследовании.
На прошлом шаге мы столкнулись с ситуацией, когда в базовом классе есть закрытое поле, к которому обращается открытый метод. Метод наследуется в производном классе и при вызове из объекта производного класса обращается к полю, которое не наследуется. Это ненаследуемое поле физически наличествует у объекта, но оно недоступно для прямого обращения: мы не можем даже в теле производного класса напрямую обратиться к полю только через унаследованный метод. Такая ситуация может показаться, на первый взгляд, странной, но в действительности это очень разумный механизм. Понять, как он реализуется, будет легче, если учесть, что при создании объекта производного класса сначала вызывается конструктор базового класса. В рассмотренных на предыдущих шагах примерах мы конструкторы явно не описывали ни для базового класса, ни для производного класса. Поэтому использовались конструкторы по умолчанию.
Упрощенная схема создания объекта производного класса в этом случае выглядит так: сначала вызывается конструктор по умолчанию базового класса, и для всех полей объекта, в том числе и закрытых, выделяется место в памяти. Затем в игру вступает собственно конструктор по умолчанию для производного класса, в результате чего место выделяется для тех полей, которые описаны непосредственно в производном классе. Поэтому, даже если поле в базовом классе является закрытым, в объекте производного класса оно все равно будет присутствовать, поскольку место под это поле выделяется при вызове конструктора базового класса.
Теперь представим себе, что в базовом классе несколько конструкторов и среди них нет конструктора без аргументов. Сразу возникает два вопроса:
Решение проблемы состоит в особом способе описания конструктора производного класса, а именно в описании конструктора производного класса после закрывающей круглой скобки через двоеточие указывается ключевое слово 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 с использованием разных версий конструкторов. По сообщениям, которые появляются в консольном окне при создании объектов, можно проследить последовательность вызова конструкторов.
На следующем шаге мы закончим изучение этого вопроса.