- •Структуры и классы
- •Наследование реализации
- •Виртуальные методы
- •Сокрытие методов
- •Вызов базовых версий функций
- •Абстрактные классы и функции
- •Запечатанные классы и методы
- •Конструкторы производных классов
- •Добавление в иерархию конструктора
- •Добавление в иерархию конструкторов с параметрами
- •Интерфейсы
- •IDisposable — сравнительно простой интерфейс, потому что в нем определен только один метод. Большинство интерфейсов содержат гораздо большее количество методов.
- •Определение и реализация интерфейсов
- •Производные интерфейсы
Добавление в иерархию конструкторов с параметрами
Начнем с конструктора с одним параметром для GenericCustomer, который обеспечит создание экземпляров заказчиков только при условии указания имени:
abstract class GenericCustomer
{
private string name;
public GenericCustomer(string name)
{
this.name = name;
}
Пока все хорошо. Однако, как уже было сказано, это приведет к ошибке компиляции, когда компилятор попытается создать конструктор по умолчанию для любого производного класса, потому что сгенерированный компилятором конструктор по умолчанию для Nevermore60Customer попытается вызвать конструктор без параметров GenericCustomer, а класс GenericCustomer не предоставляет такого конструктора. Во избежание такой ошибки компиляции, потребуется предусмотреть собственный конструктор для производного класса:
class Nevermore60Customer:GenericCustomer
{
private uint highCostMinutesUsed;
public Nevermore60Customer(string name):base(name)
{
}
Теперь создание экземпляров объектов Nevermore60Customer возможно только при условии указания строки имени заказчика, что и требовалось в любом случае. Интересно, что конструктор Nevermore60Customer делает с этой строкой. Вспомните, что он не может инициализировать поле name сам по себе, поскольку не имеет доступа к приватным полям базового класса. Вместо этого он передает строку имени на обработку конструктору базового класса GenericCustomer. Для этого указывается, что первым будет выполнен конструктор базового класса, принимающий имя в параметре. Ничего другого помимо этого конструктор не делает.
Теперь посмотрим, что произойдет, если в иерархии классов будут присутствовать различные перегрузки конструкторов. Например, предположим, что клиенты, подключаемые по тарифному плану Nevermore60, могут обращаться к MortimerPhones по совету друга, участвующего в программе "подключи друга и получи скидку". Это значит, что при конструировании Nevermore60Customer может понадобиться вместе с именем клиента передавать имя друга, который его привел. В реальной жизни при этом конструктор должен будет делать что-то сложное с именем (например, вычислять скидку для друга), но пока ограничимся только сохранением его имени в отдельном поле.
Теперь определение Nevermore60Customer будет выглядеть следующим образом:
class Nevermore60Customer: GenericCustomer
{
public Nevermore60Customer(string name, string referrerName)
: base(name)
{
this.referrerName = referrerName;
}
private string referrerName;
private uint highCostMinutesUsed;
Этот конструктор принимает имя и передает его на обработку конструктору GenericCustomer. При этом referrerName переменная, ответственность за которую несет класс Nevermore60Customer, потому упомянутый параметр обрабатывается в основном теле конструктора.
Однако не все экземпляры Nevermore60Customer будут иметь друга, поэтому нам по-прежнему необходим конструктор, который не требует второго параметра (или конструктор, присваивающий ему значение по умолчанию). Фактически, если друга нет, то значение refererName должно будет установлено равным "<None>" за счет использования следующего конструктора с одним параметром:
public Nevermore60Customer(string name)
: this(name, "<None>")
{
}
Теперь мы имеем корректно настроенный конструктор. Поучительно будет рассмотреть цепочку событий, которые произойдут при выполнении строки вроде следующей:
GenericCustomer customer = new Nevermore60Customer("Arabel Jones");
Компилятор видит, что ему нужен конструктор с одним параметром, принимающий одну строку, поэтому он идентифицирует тот, который вы определили последним:
public Nevermore60Customer(string Name)
: this(Name, "<None>")
Этот конструктор будет вызван при создании экземпляра customer. Он немедленно передаст управление соответствующему конструктору Nevermore60Customer с двумя параметрами, переслав ему значения "Arabel Jones" и "<None>". Посмотрев на код этого конструктора, вы увидите, что тот, в свою очередь, немедленно передает управление конструктору GenericCustomer с одним параметром, значение которого в данном случае "Arabel Jones", а последний передает управление конструктору по умолчанию System.Object. Только здесь начнется, собственно, вся работа конструкторов. Сначала выполнится конструктор System.Object. Дальше отработает конструктор GenericCustomer, который инициализирует поле name. После этого конструктор Nevermore6OCustomer с двумя параметрами получит управление обратно и выполнит инициализацию поля referrerName значением "<None>". И, наконец, управление получит конструктор Nevermore60Customer с одним параметром, который больше ничего не делает.
Как видите, это очень четкий и хорошо продуманный процесс. Каждый конструктор обрабатывает инициализацию переменных, которые очевидно находятся в зоне его ответственности, и в процессе корректно создается и инициализируется экземпляр вашего класса. Если вы последуете тем же принципам при написании собственных конструкторов для своих классов, то увидите, что даже наиболее сложные классы инициализируются гладко и без проблем.
Модификаторы
Ранее вы уже сталкивались с некоторыми из так называемых модификаторов — ключевыми словами, которые могут быть применены к типу или члену. Модификаторы могут указывать видимость метода, как, например public или private, или же их природу, например, virtual или abstract. В языке С# определено множество модификаторов, и сейчас стоит потратить некоторое время на ознакомление с их полным списком.
Модификаторы видимости
Модификаторы видимости указывают, какие другие единицы кода могут видеть элемент (табл. 4.1).
Таблица 4.1. Модификаторы видимости
Модификатор |
К чему относится |
Описание |
public |
К любым типам или членам |
Элемент виден в любом другом коде |
protected |
К любому члену типа, а также к любому вложенному типу |
Элемент видим только любому производному типу |
internal |
К любым типам или членам |
Элемент видим только в пределах включающей его сборки |
private |
К любому члену типа, а также к любому вложенному типу |
Элемент видим только в пределах типа, которому он принадлежит |
protected internal |
К любому члену типа, а также к любому вложенному типу |
Элемент видим только в пределах включающей его сборки, а также в любом коде внутри производного типа |
Следует обратить внимание, что определения типа могут быть общедоступными или приватными в зависимости от того, хотите ли вы обеспечить его видимость извне сборки.
public class MyClass
{
// и т.д.
Указывать модификаторы protected, private или protected internal для типов нельзя, поскольку эти уровни видимости не имеют смысла для типа, находящегося в пространстве имен. Это значит, что они могут относиться только к членам. Однако возможно создавать вложенные типы (т.е. типы, содержащиеся внутри других типов) с такой видимостью, поскольку в этом случае типы имеют статус члена. Таким образом, приведенный ниже код вполне корректен:
public class OuterClass
{
protected class InnerClass
{
// и т.д.
}
// и т.д.
}
Если есть вложенный тип, он всегда может иметь доступ ко всем членам внешнего типа. Таким образом, в последнем примере любой код внутри InnerClass всегда имеет доступ ко всем членам OuterClass, даже если они объявлены как private.
Другие модификаторы
Модификаторы, перечисленные в табл. 4.2, могут быть применены к членам типов и характеризуются различным использованием. Некоторые из них также имеет смысл использовать для типов.
Таблица 4.2. Другие модификаторы
Модификатор |
К чему относится |
Описание |
new |
К функциям-членам |
Член скрывает унаследованный член с той же сигнатурой |
static |
Только к классам и функциям-членам |
Член не связан с конкретным экземпляром класса |
virtual |
К любым типам или членам |
Член может быть переопределен в классах- наследниках |
abstract |
Только к функциям-членам |
Виртуальный член, определяющий сигнатуру, но не предоставляющий реализации |
override |
Только к функциям-членам |
Член переопределяет унаследованный виртуальный или абстрактный член базового класса |
sealed |
К классам, методам и свойствам |
Для классов означает, что от таких классов нельзя наследовать. Для свойств и методов член переопределяет унаследованный виртуальный член, но не может быть переопределен ни одним членом производных классов. Должен применяться в сочетании с override
|
extern |
Только к статическим методам [Dllimport] |
Член реализован внешне, на другом языке |