Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Добавление в иерархию конструкторов с параметрами




Начнем с конструктора с одним параметром для 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] Член реализован внешне, на другом языке

 

Интерфейсы

Как упоминалось ранее, наследуя интерфейс, класс тем самым декларирует, что он реа­лизует определенные функции. Поскольку не все объектно-ориентированные языки под­держивают интерфейсы, в этом разделе подробно описана реализация интерфейсов С#.

В этом разделе интерфейсы рассматриваются путем представления полного определе­ния одного из интерфейсов от Microsoft - System.IDisposable. Интерфейс IDisposableсодержит один метод Dispose(), предназначенный для реализации классами, которые осуществляют очистку кода:

 

public interface IDisposable

{

void Dispose();

}

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

Создавать экземпляр интерфейса нельзя - он содержит только сигнатуры своих чле­нов. Интерфейс не имеет конструкторов (как можно сконструировать нечто, экземпляр чего не создается?), равно как и полей (поскольку это подразумевает некоторую внутрен­нюю реализацию). Определению интерфейса также не разрешено содержась перегрузки операций, причем не потому, что с этим связаны какие-то принципиальные проблемы. Причина в том, что назначение интерфейсов состоит в том, чтобы служить общедоступ­ными контрактами, для которых перегрузка операций вызывала бы определенные пробле­мы совместимости с другими языками.NET, такими как Visual Basic и.NET, которые не поддерживают перегрузку операций.

Также при определении членов интерфейса не разрешены модификаторы. Члены ин­терфейса всегда неявно являются publicи не могут быть virtualили static. Это ос­тавлено на усмотрение реализаций классов. Таким образом, вполне нормально, указывать модификаторы доступа к членам интерфейса в реализующих их классах, что и делается в примерах настоящего раздела.

Рассмотрим, например, интерфейс IDisposable. Если класс пожелает объявить, что он реализует метод Dispose(), то он должен будет реализовать интерфейс IDisposable, что в терминах C# означает, что он наследуется от IDisposable.

class SomeClass: IDisposable

{

// Этот класс.ДОЛЖЕН содержать реализацию

// метода IDisposable.Dispose (), иначе

// возникнет ошибка компиляции.

public void Dispose ()

{

// реализация метода Dispose()

}

// остальная часть класса

}

В этом примере, если SomeClassбудет наследовать IDisposable, но не будет содер­жать реализации Dispose(), в точности совпадающей с сигнатурой, определенной в IDisposable, будет выдана ошибка компиляции, поскольку в этом случае класс нарушит контракт реализации интерфейса IDisposable. Разумеется, для компилятора не будет ни­какой проблемы, если встретится класс, включающий метод Dispose(), но не унаследо­ванный от IDisposable. Проблема будет в том, что другой код не будет и\<еть возможно­сти распознать, что SomeClassсогласен поддерживать средства IDisposable.

IDisposable — сравнительно простой интерфейс, потому что в нем определен только один метод. Большинство интерфейсов содержат гораздо большее ко­личество методов.

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...