Добавление в иерархию конструкторов с параметрами
Начнем с конструктора с одним параметром для 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 class MyClass { // и т.д. Указывать модификаторы protected, privateили protected internalдля типов нельзя, поскольку эти уровни видимости не имеют смысла для типа, находящегося в пространстве имен. Это значит, что они могут относиться только к членам. Однако возможно создавать вложенные типы (т.е. типы, содержащиеся внутри других типов) с такой видимостью, поскольку в этом случае типы имеют статус члена. Таким образом, приведенный ниже код вполне корректен: public class OuterClass { protected class InnerClass { // и т.д. } // и т.д. } Если есть вложенный тип, он всегда может иметь доступ ко всем членам внешнего типа. Таким образом, в последнем примере любой код внутри InnerClassвсегда имеет доступ ко всем членам OuterClass, даже если они объявлены как private. Другие модификаторы Модификаторы, перечисленные в табл. 4.2, могут быть применены к членам типов и характеризуются различным использованием. Некоторые из них также имеет смысл использовать для типов.
Таблица 4.2. Другие модификаторы
Интерфейсы Как упоминалось ранее, наследуя интерфейс, класс тем самым декларирует, что он реализует определенные функции. Поскольку не все объектно-ориентированные языки поддерживают интерфейсы, в этом разделе подробно описана реализация интерфейсов С#. В этом разделе интерфейсы рассматриваются путем представления полного определения одного из интерфейсов от 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 Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...
|