1629295407-c61bfe4caba98380ea3e7cdae6295416 (846200), страница 54
Текст из файла (страница 54)
Он вызывает конструкторпо умолчании класса TwoDShape. */public Triangle() {style = "null";}// Конструктор с параметрами.public Triangle(string s, double w, double h) : base(w, h) {style = s;294Часть I. Язык C#}// Создаем равнобедренный треугольник.public Triangle(double x) : base(x) {style = "равнобедренный";}// Метод возвращает значение площади треугольника.public double area() {return width * height / 2;}}// Метод отображает тип треугольника.public void showStyle() {Console.WriteLine("Треугольник " + style);}// Продолжаем иерархию классов треугольников.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 {public 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());Глава 11. Наследование295При выполнении этой программы получаем следующие результаты.Информация о t1:Треугольник прямоугольныйШирина и высота равны 8 и 12Цвет синийПлощадь равна 48Информация о t2:Треугольник равнобедренныйШирина и высота равны 2 и 2Цвет красныйПлощадь равна 2Благодаря наследованию класс ColorTriangle может использовать ранееопределенные классы Triangle и TwoDShape, добавляя только ту информацию, котораянеобходима для собственного (специального) применения.
В этом и состоит ценностьнаследования: оно позволяет использовать код многократно.Этот пример иллюстрирует еще один важный момент: base всегда ссылается наконструктор “ближайшего” производного класса. Так, в классе ColorTriangle ссылкаbase вызывает конструктор, определенный в классе Triangle. В классе Triangleссылка base вызывает конструктор, определенный в классе TwoDShape. Если в иерархииклассов конструктору базового класса требуются параметры, все производные классыдолжны передавать эти параметры, независимо от того, нужны ли эти параметры самомупроизводному классу.Последовательность вызова конструкторовУ читателя может возникнуть вопрос: какой конструктор выполнится первым присоздании объекта производного класса — определенный в производном классе или вбазовом? Например, если класс B — производный от класса А, то конструктор класса Абудет вызван до вызова конструктора класса B, или наоборот? Ответ звучит так.
В иерархииклассов конструкторы вызываются в порядке выведения классов, т.е. начиная сконструктора базового класса и заканчивая конструктором производного класса. Болеетого, этот порядок не нарушается, независимо от использования ссылки base.
Если ссылкаbase не используется, будут выполнены конструкторы по умолчанию (т.е. конструкторыбез параметров) всех базовых классов. Порядок выполнения конструкторовдемонстрируется в следующей программе:// Демонстрация порядка выполнения конструкторов.using System;// Создаем базовый класс.class A {public A() {Console.WriteLine("Создание класса A.");}}// Создаем класс, производный от A.class B : A {public B() {Console.WriteLine("Создание класса B.");}296Часть I. Язык C#}// Создаем класс, производный от B.class C : B {public C() {Console.WriteLine("Создание класса C.");}}class OrderOfConstruction {public static void Main() {C c = new C();}}Вот результаты, сгенерированные программой:Создание класса А.Создание класса В.Создание класса С.Как видите, конструкторы вызываются в порядке выведения классов.И в этом есть логика.
Поскольку базовый класс “ничего не знает” о производном, тодействия по инициализации, которые он должен выполнить, никак не связаны ссуществованием производного класса. Более того, они (действия) могут быть необходимыкак обязательное условие (предпосылка) инициализации, выполняемой производнымклассом в форме вызова его конструктора. Потому-то конструктор базового классавыполняется первым.Ссылки на базовый класс и объектыпроизводных классовКак вы знаете, C# — строго типизированный язык.
За исключением стандартного иавтоматического преобразований, которые применяются к простым типам, совместимостьтипов строго соблюдается. Следовательно, ссылочная переменная одного “классового” типаобычно не может ссылаться на объект другого “классового” типа. Рассмотрим, например,следующую программу:// Эта программа не скомпилируется.class X {int a;public X(int i) {a = i;}}class Y {int a;public Y(int i) {a = i;}}class IncompatibleRef {public static void Main() {X x = new X(10);X x2;Глава 11. Наследование297}Y y = new Y(5);x2 = x; // OK, обе переменные имеют одинаковый тип.x2 = y; // Ошибка, здесь переменные разного типа.}Несмотря на то что здесь классы X и Y физически представляют собой одно и то же,невозможно присвоить объект класса Y ссылочной переменной типа X, поскольку ониимеют разные типы.
В общем случае ссылочная переменная может ссылаться только наобъекты своего типа.Однако существует важное исключение из C#-требования строгой совместимоститипов. Ссылочную переменную базового класса можно присвоить ссылке на объект любогокласса, выведенного из этого базового класса. Рассмотрим пример.// Ссылка на базовый класс может указывать на// объект производного класса.using System;class X {public int a;}public X(int i) {a = i;}class Y : X {public int b;}public Y(int i, int j) : base(j) {b = i;}class BaseRef {public static void Main() {X x = new X(10);X x2;Y y = new Y(5, 6);x2 = x; // OK, обе переменные имеют одинаковый тип.Console.WriteLine("x2.a: " + x2.a);x2 = y;// Все равно ok, поскольку класс Y// выведен из класса X.Console.WriteLine("x2.a: " + x2.a);//}}// Х-ссылки "знают" только о членах класса X.x2.a = 19; // ОКx2.b = 27; // Ошибка, в классе X нет члена b.На этот раз класс Y — производный от класса X, поэтому допустимо ссылке x2присвоить ссылку на объект класса Y.298Часть I.
Язык C#Важно понимать, что именно тип ссылочной переменной (а не тип объекта, накоторый она ссылается) определяет, какие члены могут быть доступны. Другими словами,когда ссылка на производный класс присваивается ссылочной переменной базового класса,вы получаете доступ только к тем частям объекта, которые определены базовым классом.Вот почему ссылка x2 не может получить доступ к члену b класса Y даже при условии, чтоона указывает на объект класса Y.
И это вполне логично, поскольку базовый класс “неимеет понятия” о том, что добавил в свой состав производный класс. Поэтому последняястрока программы представлена как комментарий.И хотя последний абзац может показаться несколько “эзотерическим”, он имеет рядважных практических приложений.
Одно из них описано в этом разделе, а другое — ниже вэтой главе при рассмотрении виртуальных методов.Важность присвоения ссылок на производный класс ссылочным переменнымбазового класса ощущается в случае, когда в иерархии классов вызываются конструкторы.Как вы знаете, считается нормальным определить для класса конструктор, который вкачестве параметра принимает объект своего класса. Это позволяет классу создать копиюобъекта. Классы, выведенные из такого класса, могут из этого факта извлечь определеннуюпользу. Рассмотрим, например, следующие версии классов TwoDShape и Triangle, В обакласса добавлены конструкторы, которые в качестве параметра принимают объект.// Передача ссылки на производный класс// ссылке на базовый класс.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 TwoDShape(TwoDShape ob) {width = ob.width;height = ob.height;}// Свойства width и height.public double width {get { return pri_width; }set { pri_width = value; }}Глава 11.
Наследование299public double height {get { return pri_height; }set { pri_height = value; }}}public void showDim() {Console.WriteLine("Ширина и высота равны " +width + " и " + height);}// Класс треугольников, производный от класса TwoDShape.class Triangle : TwoDShape {string style; // Закрытый член.// Конструктор по умолчанию.public Triangle() {style = "null";}// Конструктор класса Triangle.public Triangle(string s,double w,double h) : base(w, h) {style = s;}// Создаем равнобедренный треугольник.public Triangle(double x) : base(x) {style = "равнобедренный";}// Создаем объект из объекта.public Triangle(Triangle ob) : base(ob) {style = ob.style;}}// Метод возвращает площадь треугольника.public double area() {return width * height / 2;}// Метод отображает тип треугольника.public void showStyle() {Console.WriteLine("Треугольник " + style);}class Shapes7 {public static void Main() {Triangle t1 = new Triangle("прямоугольный", 8.0, 12.0);// Создаем копию объекта t1.Triangle t2 = new Triangle(t1);Console.WriteLine("Информация о t1: ");t1.showStyle();t1.showDim();300Часть I.