Книга: C# 4.0: полное руководство
Конструкторы и наследование
Конструкторы и наследование
В иерархии классов допускается, чтобы у базовых и производных классов были свои собственные конструкторы. В связи с этим возникает следующий резонный вопрос: какой конструктор отвечает за построение объекта производного класса: конструктор базового класса, конструктор производного класса или же оба? На этот вопрос можно ответить так: конструктор базового класса конструирует базовую часть объекта, а конструктор производного класса — производную часть этого объекта. И в этом есть своя логика, поскольку базовому классу неизвестны и недоступны любые элементы производного класса, а значит, их конструирование должно происходить раздельно. В приведенных выше примерах данный вопрос не возникал, поскольку они опирались на автоматическое создание конструкторов, используемых в C# по умолчанию. Но на практике конструкторы определяются в большинстве классов. Ниже будет показано, каким образом разрешается подобная ситуация.
Если конструктор определен только в производном классе, то все происходит очень просто: конструируется объект производного класса, а базовая часть объекта автоматически конструируется его конструктором, используемым по умолчанию. В качестве примера ниже приведен переработанный вариант класса Triangle
, в котором определяется конструктор, а член Style
делается закрытым, так как теперь он устанавливается конструктором.
// Добавить конструктор в класс Triangle,
using System;
// Класс для двумерных объектов.
class TwoDShape {
double pri_width;
double pri_height;
// Свойства ширины и длины объекта,
public double Width {
get { return pri_width; }
set { pri_width = value < 0 ? -value : value; }
}
public double Height {
get { return pri_height; }
set { pri_height = value < 0 ? -value : value; }
}
public void ShowDim() {
Console.WriteLine("Ширина и длина равны " +
Width + " и " + Height);
}
}
// Класс для треугольников, производный от класса TwoDShape.
class Triangle : TwoDShape {
string Style;
// Конструктор.
public Triangle(string s, double w, double h) {
Width = w; // инициализировать член базового класса
Height = h; // инициализировать член базового класса
Style = s; // инициализировать член производного класса
}
// Возвратить площадь треугольника,
public double Area() {
return Width * Height / 2;
}
// Показать тип треугольника,
public void ShowStyle() {
Console.WriteLine("Треугольник " + Style);
}
}
class Shapes3 {
static void Main() {
Triangle t1 = new Triangle("равнобедренный", 4.0, 4.0);
Triangle t2 = new Triangle("прямоугольный", 8.0, 12.0);
Console.WriteLine("Сведения об объекте t1: ");
t1.ShowStyle();
t1.ShowDim();
Console.WriteLine("Площадь равна " + t1.Area());
Console.WriteLine();
Console.WriteLine("Сведения об объекте t2: ");
t2.ShowStyle();
t2.ShowDim() ;
Console.WriteLine("Площадь равна " + t2.Area());
}
}
В данном примере конструктор класса Triangle
инициализирует наследуемые члены класса TwoDShape
вместе с его собственным полем Style
.
Когда конструкторы определяются как в базовом, так и в производном классе, процесс построения объекта несколько усложняется, поскольку должны выполняться конструкторы обоих классов. В данном случае приходится обращаться к еще одному ключевому слову языка С#: base
, которое находит двоякое применение: во-первых, для вызова конструктора базового класса; и во-вторых, для доступа к члену базового класса, скрывающегося за членом производного класса. Ниже будет рассмотрено первое применение ключевого слова base
.
Вызов конструкторов базового класса
С помощью формы расширенного объявления конструктора производного класса и ключевого слова base
в производном классе может быть вызван конструктор, определенный в его базовом классе. Ниже приведена общая форма этого расширенного объявления:
конструктор_производного_класса{список_параметров) : base (список_аргументов) {
// тело конструктора
}
где список_аргументов обозначает любые аргументы, необходимые конструктору в базовом классе. Обратите внимание на местоположение двоеточия.
Для того чтобы продемонстрировать применение ключевого слова base
на конкретном примере, рассмотрим еще один вариант класса TwoDShape
в приведенной ниже программе. В данном примере определяется конструктор, инициализирующий свойства Width
и Height
. Затем этот конструктор вызывается конструктором класса Triangle
.
// Добавить конструктор в класс TwoDShape.
using System;
// Класс для двумерных объектов,
class TwoDShape {
double pri_width;
double pri_height;
// Конструктор класса TwoDShape.
public TwoDShape(double w, double h) {
Width = w;
Height = h;
}
public double Width {
get { return pri_width; }
set { pri_width = value < 0 ? -value : value; }
}
public double Height {
get { return pri_height; }
set { pri_height = value < 0 ? -value : value; }
}
public void ShowDim() {
Console.WriteLine("Ширина и высота равны " +
Width + " и " + Height);
}
}
// Класс для треугольников, производный от класса TwoDShape.
class Triangle : TwoDShape {
string Style;
// Вызвать конструктор базового класса.
public Triangle(string s, double w, double h) : base(w, h){
Style = s;
}
// Возвратить площадь треугольника,
public double Area() {
return Width * Height / 2;
}
// Показать тип треугольника,
public void ShowStyle() {
Console.WriteLine("Треугольник " + Style);
}
}
class Shapes4 {
static void Main() {
Triangle t1 = new Triangle("равнобедренный", 4.0, 4.0);
Triangle t2 = new Triangle("прямоугольный", 8.0, 12.0);
Console.WriteLine("Сведения об объекте t1: ");
t1.ShowStyle();
t1.ShowDim();
Console.WriteLine("Площадь равна " + t1.Area());
Console.WriteLine();
Console.WriteLine("Сведения об объекте t2: ");
t2.ShowStyle();
t2.ShowDim();
Console.WriteLine("Площадь равна " + t2.Area());
}
}
Теперь конструктор класса Triangle объявляется следующим образом.
public Triangle(
string s, double w, double h) : base(w, h) {
В данном варианте конструктор Triangle()
вызывает метод base
с параметрами w и h. Это, в свою очередь, приводит к вызову конструктора TwoDShape(),
инициализирующего свойства Width
и Height
значениями параметров w и h. Они больше не инициализируются средствами самого класса Triangle
, где теперь остается инициализировать только его собственный член Style
, определяющий тип треугольника. Благодаря этому класс TwoDShape
высвобождается для конструирования своего подобъекта любым избранным способом. Более того, в класс TwoDShape
можно ввести функции, о которых даже не будут подозревать производные классы, что предотвращает нарушение существующего кода.
С помощью ключевого слова base
можно вызвать конструктор любой формы, определяемой в базовом классе, причем выполняться будет лишь тот конструктор, параметры которого соответствуют переданным аргументам. В качестве примера ниже приведены расширенные варианты классов TwoDShape
и Triangle
, в которые включены как используемые по умолчанию конструкторы, так и конструкторы, принимающие один аргумент.
// Добавить дополнительные конструкторы в класс TwoDShape.
using System;
class TwoDShape {
double pri_width;
double pri_height;
// Конструктор, вызываемый по умолчанию,
public TwoDShape() {
Width = Height = 0.0;
}
// Конструктор класса TwoDShape.
public TwoDShape(double w, double h) {
Width = w;
Height = h;
}
// Сконструировать объект равной ширины и высоты,
public TwoDShape(double x) {
Width = Height = x;
}
// Свойства ширины и высоты объекта,
public double Width {
get { return pri_width; }
set { pri_width = value < 0 ? -value : value; }
}
public double Height {
get { return pri_height; }
set { pri_height = value < 0 ? -value : value; }
}
public void ShowDim() {
Console.WriteLine("Ширина и высота равны " +
Width + " и " + Height);
}
}
// Класс для треугольников, производный от класса TwoDShape.
class Triangle : TwoDShape {
string Style;
/* Конструктор, используемый по умолчанию.Автоматически вызывает конструктор, доступный по умолчанию в классе TwoDShape. */
public Triangle() {
Style = "null";
}
// Конструктор, принимающий три аргумента,
public Triangle(
string s, double w, double h) : base(w, h) {
Style = s;
}
// Сконструировать равнобедренный треугольник,
public Triangle(double x) : base(x) {
Style = "равнобедренный";
}
// Возвратить площадь треугольника,
public double Area() {
return Width * Height / 2;
}
// Показать тип треугольника,
public void ShowStyle() {
Console.WriteLine("Треугольник " + Style);
}
}
class Shapes5 {
static void Main() {
Triangle t1 = new Triangle();
Triangle t2 = new Triangle("прямоугольный", 8.0, 12.0);
Triangle t3 = new Triangle(4.0);
t1 = t2;
Console.WriteLine("Сведения об объекте t1: ");
t1.ShowStyle();
t1.ShowDim();
Console.WriteLine("Площадь равна " + t1.Area());
Console.WriteLine();
Console.WriteLine("Сведения об объекте t2: ");
t2.ShowStyle();
t2.ShowDim();
Console.WriteLine("Площадь равна " + t2.Area());
Console.WriteLine();
Console.WriteLine("Сведения об объекте t3: ");
t3.ShowStyle();
t3.ShowDim();
Console.WriteLine("Площадь равна " + t3.Area());
Console.WriteLine();
}
}
Вот к какому результату приводит выполнение этого кода.
Сведения об объекте t1:
Треугольник прямоугольный
Ширина и высота равны 8 и 12
Площадь равна 48
Сведения об объекте t2:
Треугольник прямоугольный
Ширина и высота равны 8 и 12
Площадь равна 48
Сведения об объекте t3:
Треугольник равнобедренный
Ширина и высота равны 4 и 4
Площадь равна 8
А теперь рассмотрим вкратце основные принципы действия ключевого слова base
. Когда в производном классе указывается ключевое слово base
, вызывается конструктор из его непосредственного базового класса. Следовательно, ключевое слово base
всегда обращается к базовому классу, стоящему в иерархии непосредственно над вызывающим классом. Это справедливо даже для многоуровневой иерархии классов. Аргументы передаются базовому конструктору в качестве аргументов метода base().
Если же ключевое слово отсутствует, то автоматически вызывается конструктор, используемый в базовом классе по умолчанию.
- Основы наследования
- Доступ к членам класса и наследование
- Конструкторы и наследование
- Наследование и сокрытие имен
- Создание многоуровневой иерархии классов
- Порядок вызова конструкторов
- Ссылки на базовый класс и объекты производных классов
- Виртуальные методы и их переопределение
- Применение абстрактных классов
- Предотвращение наследования с помощью ключевого слова sealed
- Класс object
- ГЛАВА 11 Наследование
- ГЛАВА 3 Выдающиеся конструкторы
- Наследование и конструкторы
- Наследование конструкторов
- Наследование
- Конструкторы по умолчанию
- 9.2. Классы и конструкторы
- У14.4 Наследование без классов
- Конструкторы
- Лекция 9. Наследование и замыкание
- Лекция 14. Введение в наследование
- Лекция 15. Множественное наследование