Порядок вызовов конструкторов при наследовании

Наследование

 

Наследование – это средство для получения новых классов (типов) из существующих классов (типов). Класс, от которого осуществляется наследование, называется базовым классом или классом родителем, а класс наследник называют производным или порожденным классом.

В C# для классов не поддерживается множественное наследование.

Синтаксис наследования:

 

сlass < производный класс >: < базовый класс >

{

.

. //Члены производного класса

.

}

Члены базового класса объявленные со спецификацией private не наследуются. Остальные члены базового класса становятся членами производного класса с теми же спецификациями доступа, что и в базовом классе.

Пример:

using System;

namespace ConsoleApplication1

{

class CA

{

protected int x = 5;

public int y = 25;

protected int z;

public CA ( )

{ z = 0;}

public void put ( )

{

Console.WriteLine (z);

}

}

class CB : CA //Порождение нового класса

{

public CB ( )

{

z = x + y;

}

public void put ( )

{

Console.WriteLine (z);

}

}

class Class1

{

static void Main(string[] args)

{

CA ob = new CA ( );

ob. put ( );

CB ob1 = new CB ( );

ob1. put ( );

}

 

}

}

Предположим, имеются иерархия классов, представленная в виде дерева наследования на рисунке:

 

Рис. 4.

При создании объекта производного класса, например класса CD, конструктор этого класса, перед выполнением своего кода, всегда вызывает конструктор по умолчанию базового класса, а тот в свою очередь, конструктор по умолчанию класса родителя и т.д. до самого верхнего уровня иерархии. Таким образом, всегда вначале работает код конструктора по умолчанию класса СА, затем классов CB и CD вниз по дереву наследования.

Если в классах родителях присутствует хотя бы один конструктор с аргументами, то конструкторы по умолчанию должны быть явно определены во всех родительских классах, иначе произойдет ошибка на этапе создания объекта производного класса, т.к. цепочка вызовов конструкторов будет нарушена. Дело в том, что компилятор создает конструктор по умолчанию только в том случае, если программист не объявил ни одного конструктора в классе.

Нельзя объявлять конструктор по умолчанию со спецификацией private, если необходимо создавать производные классы при помощи наследования. Private – конструктор по умолчанию – запрещает наследование. Такого же эффекта можно добиться, если запечатать класс, или другими словами – изолировать класс.

 

Изолированные классы

 

Классы, от которых запрещено наследовать, называются запечатанными или изолированными классами.

Изолировать класс можно с помощью ключевого слова sealed.

Например:

sealed class CM

{

int x;

int y;

:

}

 

Попытка наследования:

class CA : CM

{

:

}

приведет к ошибке трансляции.

Запрещение наследования обычно применяется для классов, предназначенных для выполнения ответственных операций, вмешательство в работу которых не допустимо. К таким классам могут относиться, например, классы для осуществления банковских операций или классы, предназначенные для управления сложным физическим устройством.

 


Сокрытие методов базового класса

 

Если в производном классе объявить функцию с точно таким же заголовком, что и в базовом классе, то говорят, что новый метод сокроет метод базового класса.

Для метода, скрывающего метод базового класса, необходимо указать ключевое слово new, которое говорит, что вы сознательно пошли на этот шаг. Если не сделать этого, то компилятор выдает предупреждение, но ошибки не будет.

Например:

class CA

{ string strA;

public CA()

{

strA = "Привет";

}

public string func ( )

{

return strA;

}

}

class CB:CA

{

string strB;

public CB()

{

strB = "HELLO";

}

public new string func ( )

{

return strB;

}

}

class Class1

{

[STAThread]

static void Main(string[] args)

{

CA pA = new CA();

CB pB = new CB();

Console.WriteLine(pA.func()); // Привет

Console.WriteLine(pB.func()); // HELLO

}

}

 

Возможна ситуация, когда изготовитель базового класса добавил функцию, при очередной модификации библиотечных классов, заголовок которой случайно совпал с заголовком функции в вашем, производном от него, классе. В таком случае, при перекомпиляции программы с новой версией библиотеки, вы получите предупреждение компилятора, что требуется ключевое слово new, так как вы скрываете такую то функцию базового класса. Логичнее всего в такой ситуации, для избежания возможного конфликта, переименовать функцию производного класса. Либо, что очень не желательно, если вы очень уверены, что не будет ни каких отрицательных последствий, оставить, как есть.
Вопросы:

1. Что такое индексатор?

2. Сколько индексаторов может быть объявлено в классе для работы, например, с одномерными массивами?

3. Почему индексаторы называют интеллектуальными массивами?

4. Что такое наследование?

5. Разрешается ли множественное наследование от классов?

6. Какие члены базового класса могут наследоваться?

7. Каков порядок вызовов конструкторов при создании объекта производного класса?

8. Разрешается ли наследование от класса с private конструктором по умолчанию?

9. Разрешается ли наследование от класса, в котором определен конструктор с аргументами, но отсутствует конструктор по умолчанию?

10. Разрешается ли наследование от класса без конструкторов?

11. Как не явно запретить наследование?

12. С помощью какого ключевого слова можно явно запретить наследование?

13. Является ли ошибкой объявление в производном классе функции, заголовок которой совпадает с заголовком функции базового класса?

14. Что такое сокрытие функции базового класса.

15. С помощью какого ключевого слова необходимо сообщить компилятору, что вы преднамеренно скрываете функцию базового класса?
Чистый полиморфизм

 

Свойства кода вести себя по-разному в зависимости от ситуации, возникающей в момент выполнения - называется полиморфизмом.

Различают два вида полиморфизма - специальный полиморфизм и чистый полиморфизм.

Специальный полиморфизм реализуется с помощью перегруженных функций.

Чистый полиморфизм - связан с механизмом наследования и реализуется через виртуальные функции.

В C# в отличие от С вызов некоторых функций на этапе трансляции только обозначается без точного указания какая именно функция вызывается. Какая из возможных функций будет вызвана, определяется на этапе выполнения программы. Такой процесс называется поздним связыванием.

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

Перегруженная функция - член выбирается во время компиляции, т.е. для нее осуществляется раннее связывание.

Возможность динамически выбирать функцию-член, в зависимости от объекта, осуществляется с помощью виртуальных функций.