Текст книги "C# 4.0: полное руководство"
Автор книги: Герберт Шилдт
Жанр:
Программирование
сообщить о нарушении
Текущая страница: 25 (всего у книги 83 страниц)
В иерархии классов допускается, чтобы у базовых и производных классов были свои собственные конструкторы. В связи с этим возникает следующий резонный вопрос: какой конструктор отвечает за построение объекта производного класса: конструктор базового класса, конструктор производного класса или же оба? На этот вопрос можно ответить так: конструктор базового класса конструирует базовую часть объекта, а конструктор производного класса – производную часть этого объекта. И в этом есть своя логика, поскольку базовому классу неизвестны и недоступны любые элементы производного класса, а значит, их конструирование должно происходить раздельно. В приведенных выше примерах данный вопрос не возникал, поскольку они опирались на автоматическое создание конструкторов, используемых в 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().
Если же ключевое слово отсутствует, то автоматически вызывается конструктор, используемый в базовом классе по умолчанию.
В производном классе можно определить член с таким же именем, как и у члена его базового класса. В этом случае член базового класса скрывается в производном классе. И хотя формально в C# это не считается ошибкой, компилятор все же выдаст сообщение, предупреждающее о том, что имя скрывается. Если член базового класса требуется скрыть намеренно, то перед его именем следует указать ключевое слово new, чтобы избежать появления подобного предупреждающего сообщения. Следует,
однако, иметь в виду, что это совершенно отдельное применение ключевого слова new, не похожее на его применение при создании экземпляра объекта.
Ниже приведен пример сокрытия имени.
// Пример сокрытия имени с наследственной связью.
using System;
class А {
public int i = 0;
}
// Создать производный класс. j
class В : A { *
new int i; // этот член скрывает член i из класса А public В(int b) {
i = b; // член i в классе В
}
public void Show() {
Console.WriteLine("Член i в производном классе: " + i) ;
}
}
class NameHiding { static void Main() {
В ob = new В(2);
ob.Show() ;
}
}
Прежде всего обратите внимание на использование ключевого слова new в следующей строке кода.
new int i; // этот член скрывает член i из класса А
В этой строке компилятору, по существу, сообщается о том, что вновь создаваемая переменная i намеренно скрывает переменную i из базового класса А и что автору программы об этом известно. Если же опустить ключевое слово new в этой строке кода, то компилятор выдаст предупреждающее сообщение.
Вот к какому результату приводит выполнение приведенного выше кода.
Член i в производном классе: 2
В классе В определяется собственная переменная экземпляра i, которая скрывает переменную i из базового класса А. Поэтому при вызове метода Show() для объекта типа В выводится значение переменной i, определенной в классе В, а не той, что определена в классе А.
Применение ключевого слова base для доступа к скрытому имени
Имеется еще одна форма ключевого слова base
, которая действует подобно ключевому слову this
, за исключением того, что она всегда ссылается на базовый класс в том производном классе, в котором она используется. Ниже эта форма приведена в общем виде:
base.член
где член
может обозначать метод или переменную экземпляра. Эта форма ключевого слова base чаще всего применяется в тех случаях, когда под именами членов производного класса скрываются члены базового класса с теми же самыми именами. В качестве примера ниже приведен другой вариант иерархии классов из предыдущего примера.
// Применение ключевого слова base для преодоления
// препятствия, связанного с сокрытием имен.
using System;
class А {
public int i = 0;
}
// Ссзздать производный класс,
class В : А {
new int i; // этот член скрывает член i из класса А
public В(int a, int b) {
base.i = a; // здесь обнаруживается скрытый член из класса А
i = b; // член i из класса В
}
public void Show() {
// Здесь выводится член i из класса А.
Console.WriteLine("Член i в базовом классе: " + base.i);
// А здесь выводится член i из класса В.
Console.WriteLine("Член i в производном классе: " + i);
}
}
class UncoverName {
static void Main() {
В ob = new В(1, 2);
ob.Show();
}
}
Выполнение этого кода приводит к следующему результату.
Член i в базовом классе: 1
Член i в производном классе: 2
Несмотря на то что переменная экземпляра i в производном классе В
скрывает переменную i из базового класса А
, ключевое слово base
разрешает доступ к переменной i, определенной в базовом классе.
С помощью ключевого слова base
могут также вызываться скрытые методы. Например, в приведенном ниже коде класс В
наследует класс А
и в обоих классах объявляется метод Show()
. А затем в методе Show()
класса В
с помощью ключевого слова base
вызывается вариант метода Show()
, определенный в классе А
.
// Вызвать скрытый метод.
using System;
class А {
public int i = 0;
// Метод Show() в классе A
public void Show() {
Console.WriteLine("Член i в базовом классе: " + i);
}
}
// Создать производный класс,
class В : А {
new int i; // этот член скрывает член i из класса А
public В(int a, int b) {
base.i = a; // здесь обнаруживается скрытый член из класса А
i = b; // член i из класса В
}
// Здесь скрывается метод Show() из класса А. Обратите
// внимание на применение ключевого слова new.
new public void Show() {
base.Show(); // здесь вызывается метод Show() из класса А
// далее выводится член i из класса В
Console.WriteLine("Член i в производном классе: " + i);
}
}
class UncoverName {
static void Main() {
В ob = new В (1, 2);
ob.Show();
}
}
Выполнение этого кода приводит к следующему результату.
Член i в базовом классе: 1
Член i в производном классе: 2
Как видите, в выражении base.Show()
вызывается вариант метода Show()
из базового класса.
Обратите также внимание на следующее: ключевое слово new
используется в приведенном выше коде с целью сообщить компилятору о том, что метод Show()
, вновь объявляемый в производном классе В
, намеренно скрывает другой метод Show(),
определенный в базовом классе А
.
В представленных до сих пор примерах программ использовались простые иерархии классов, состоявшие только из базового и производного классов. Но в C# можно также строить иерархии, состоящие из любого числа уровней наследования. Как упоминалось выше, многоуровневая иерархия идеально подходит для использования одного производного класса в качестве базового для другого производного класса. Так, если имеются три класса, А, В
и С
, то класс С
может наследовать от класса В
, а тот, в свою очередь, от класса А
. В таком случае каждый производный класс наследует характерные особенности всех своих базовых классов. В частности, класс С
наследует все члены классов В
и А
.
Для того чтобы показать, насколько полезной может оказаться многоуровневая иерархия классов, рассмотрим следующий пример программы. В ней производный класс Triangle
служит в качестве базового для создания другого производного класса – ColorTriangle
. При этом класс ColorTriangle
наследует все характерные особенности, а по существу, члены классов Triangle
и TwoDShape
, к которым добавляется поле color, содержащее цвет треугольника.
// Пример построения многоуровневой иерархии классов.
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);
}
}
// Расширить класс Triangle,
class ColorTriangle : Triangle {
string color;
public ColorTriangle(string c, string s,
double w, double h) : base(s, w, h) {
color = c;
}
// Показать цвет треугольника,
public void ShowColor() {
Console.WriteLine("Цвет " + color);
}
}
class Shapes6 {
static void Main() {
ColorTriangle t1 =
new ColorTriangle(«синий», «прямоугольный», 8.0, 12.0);
ColorTriangle t2 =
new ColorTriangle(«красный», «равнобедренный», 2.0, 2.0);
Console.WriteLine("Сведения об объекте t1: ");
t1.ShowStyle();
t1.ShowDim();
t1.ShowColor();
Console .WriteLine ("Площадь равна " + t1.Area());
Console.WriteLine() ;
Console.WriteLine("Сведения об объекте t2: ");
t2.ShowStyle();
t2.ShowDim();
t2.ShowColor() ;
Console.WriteLine("Площадь равна " + t2.Area());
}
}
При выполнении этой программы получается следующей результат.
Сведения об объекте t1:
Треугольник прямоугольный
Ширина и высота равны 8 и 12
Цвет синий
Площадь равна 48
Сведения об объекте t2:
Треугольник равнобедренный
Ширина и высота равны 2 и 2
Цвет красный
Площадь равна 2
Благодаря наследованию в классе ColorTriangle
могут использоваться определенные ранее классы Triangle
и TwoDShape
, к элементам которых добавляется лишь та информация, которая требуется для конкретного применения данного класса. В этом отчасти и состоит ценность наследования, поскольку оно допускает повторное использование кода.
Приведенный выше пример демонстрирует еще одно важное положение: ключевое слово base всегда обозначает ссылку на конструктор ближайшего по иерархии базового класса. Так, ключевое слово base
в классе ColorTriangle
обозначает вызов конструктора из класса Triangle
, а ключевое слово base в классе Triangle
– вызов конструктора из класса TwoDShape
. Если же в иерархии классов конструктору базового класса требуются параметры, то все производные классы должны предоставлять эти параметры вверх по иерархии, независимо от того, требуются они самому производному классу или нет.