Главная страница Случайная страница КАТЕГОРИИ: АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника |
Конструкторы класса
Конструктор - неотъемлемый компонент класса. Нет классов, задающих тип данных и не имеющих конструкторов. Конструктор представляет собой специальный метод класса, позволяющий создавать объекты класса. У конструкторов две синтаксические особенности:
[модификаторы] < имя_класса> ([аргументы]) [: this(< аргументы>)] {//тело }
Из первого свойства следует, что конструкторы класса представляют собой множество перегруженных методов и должны отличаться сигнатурой - числом аргументов либо типами аргументов. Чтобы справиться с этим ограничением, иногда приходится в один из конструкторов добавлять фиктивный аргумент, изменяя тем самым его сигнатуру. Если в классе явно не задан ни один конструктор, то к классу автоматически добавляется конструктор по умолчанию - конструктор без аргументов. Заметьте, что если в классе явно определен один или несколько конструкторов, то автоматического добавления конструктора без аргументов не происходит. Большинство конструкторов класса имеют модификатор доступа public. Помните, что класс создается в интересах своих клиентов. Конструкторы позволяют клиентам класса создавать объекты класса и работать с ними. Конструкторы позволяют не только создать объект класса, но и задать начальные значения полей класса, то есть позволяют создать объект с заданными свойствами. Аргументы конструктора задают значения, позволяющие инициализировать поля класса. Поскольку в момент создания объекта не всегда можно задать полный набор его свойств, класс старается обеспечить клиентам широкий набор конструкторов, позволяющих создать и инициализировать объект свойствами, известными клиенту в момент создания того или иного объекта. Разберем в деталях процесс создания. Первым делом в стеке для сущностей создается ссылка, пока висячая со значением null. Затем в динамической памяти создается объект - структура данных с полями, определяемыми классом Person. Поля объекта инициализируются значениями по умолчанию: ссылочные поля - значением null, арифметические - нулями, строковые - пустой строкой. Эту работу выполняет конструктор по умолчанию, который, можно считать, всегда вызывается в начале процесса создания. Если поля класса объявлены с инициализацией, то выполняется инициализация полей объекта заданными значениями. Если вызван конструктор с аргументами, то начинает выполняться тело этого конструктора. Как правило, при этом происходит инициализация тех полей объекта, значения которых переданы конструктору. На заключительном этапе ссылка связывается с созданным объектом. Заметьте, правильный стиль требует, чтобы среди конструкторов класса всегда присутствовал конструктор по умолчанию - конструктор без аргументов. В нашем случае тело этого конструктора пусто, поскольку все поля объявлены с инициализацией и конструктору нет смысла изменять эти значения. Часто имена формальных аргументов конструктора совпадают с соответствующими именами полей класса. Поэтому при присваивании полю объекта значения аргумента необходимо уточнить имя поля именем объекта. В данном случае речь идет о текущем объекте, имя которого фиксировано - this. Это одно из возможных применений ключевого слова this - разрешать неоднозначность контекста. Другое применение состоит в вызове цепочки конструкторов. Для объявления и инициализации объекта при помощи конструктора используется следующая запись: < тип класса> имя переменной = new < тип класса> (< список_аргументов>); Например: Rational r = new Rational();Когда приходится работать с перегружаемыми конструкторами, то иногда очень полезно предоставить возможность одному конструктору вызывать другой. В С# это дается с помощью ключевого слова this. Ниже приведена общая форма такого вызова: имя_конструктора(список_параметров1): this(список_параметров2) { //... Тело конструктора, которое может быть пустым. }
В исходном конструкторе сначала выполняется перегружаемый конструктор, список параметров которого соответствует критерию список_параметров2, а затем все остальные операторы, если таковые имеются в исходном конструкторе.
Вызывать перегружаемый конструктор с помощью ключевого слова this полезно, в частности, потому, что он позволяет исключить ненужное дублирование кода. Другое преимущество организации подобного вызова перезагружаемого конструктора заключается в возможности создавать конструкторы с задаваемыми " по умолчанию" аргументами, когда эти аргументы не указаны явно. Инициализаторы объектов предоставляют способ создания объекта и инициализации его полей и свойств. Если используются инициализаторы объектов, то вместо обычного вызова конструктора класса указываются имена полей или свойств, инициализируемых первоначально задаваемым значением. Следовательно, синтаксис инициализатора объекта предоставляет альтернативу явному вызову конструктора класса. Синтаксис инициализатора объекта используется главным образом при создании анонимных типов в LINQ-выражениях. Но поскольку инициализаторы объектов можно, а иногда и должно использовать в именованном классе, то ниже представлены основные положения об инициализации объектов. Ниже приведена общая форма синтаксиса инициализации объектов: new имя_класса {имя = выражение, имя = выражение,...} где имя обозначает имя поля или свойства, т.е. доступного члена класса, на который указывает имя_класса. А выражение обозначает инициализирующее выражение, тип которого, конечно, должен соответствовать типу поля или свойства. Инициализаторы объектов обычно не используются в именованных классах, хотя это вполне допустимо. Вообще, при обращении с именованными классами используется синтаксис вызова обычного конструктора. Пример использования инициализоторов объекта: class Car { public string model; public short year; }
class Program { static void Main(string[] args) { // используем инициализаторы Car myCar = new Car { model = " Lexus", year = 2004 };
Console.ReadLine(); } } } Деструкторы класса Задача удаления ненужных объектов в C# снята с программиста и возложена на соответствующий инструментарий - сборщик мусора. В классическом варианте деструктор служит для удаления объектов и освобождения ресурсов, занятых объектом, в первую очередь оперативной памяти. В языке C# y класса может быть деструктор, но он не занимается удалением объектов и не вызывается нормальным образом в ходе выполнения программы. Деструктор класса, если он есть, вызывается автоматически в процессе сборки мусора. Его роль - в освобождении неуправляемых ресурсов, например, файлов, открытых объектом. Деструктор C# фактически является финализатором (finalizer). Методы-свойства Методы, называемые свойствами (Properties), представляют специальную синтаксическую конструкцию, предназначенную для обеспечения эффективной работы со свойствами. Напомню, правильной стратегией является закрытие полей от клиента - поля объявляются с модификатором protected или private. Клиенты класса не должны использовать информацию о том, как устроены поля.Это облегчает возможную модификацию класса в будущем. Класс сможет изменить представление данных, сохранив интерфейс, предоставляемый клиентам. В этом случае изменения в полях не отразятся на клиентах. Закрытие полей не означает, что клиенты класса не могут работать с данными, хранящимися в полях класса. Возможны различные стратегии доступа клиента к закрытым полям класса. Перечислю пять наиболее употребительных стратегий:
Для эффективной поддержки этих стратегий и введены специальные методы, называемые свойствами. Приведу вначале пример, а потом уточню синтаксис этих методов. Рассмотрим класс Person, у которого пять полей: fam, status, salary, age, health, характеризующих, соответственно, фамилию, статус, зарплату, возраст и здоровье персоны. Все поля закрыты для клиента, так что клиент не может непосредственно читать или записывать данные в поля класса. Для каждого из этих полей может быть разумной своя стратегия доступа. При проектировании класса будем предполагать, что возраст доступен для чтения и записи, фамилию можно задать только один раз, статус можно только читать, зарплата недоступна для чтения, а здоровье закрыто для доступа и только специальные методы класса могут сообщать некоторую информацию о здоровье персоны. Вот как на C# можно обеспечить эти стратегии доступа к закрытым полям класса: /// < summary> /// Класс, задающий общие свойства /// и поведение личности/// < /summary> public class Person{ public enum Status { ребенок, школьник, студент, работник, пенсионер } //поля (все закрыты) string fam = " ", health = " "; int age = 0, salary = 0; Status status = Status.работник; //методы - свойства /// < summary> ///стратегия: Read, Write-once (Чтение, запись при первом обращении) /// < /summary> public string Fam { set { if (fam == " ") fam = value; } get { return (fam); } } /// < summary> ///стратегия: Read-only(Только чтение) /// < /summary> public Status GetStatus { get { return (status); } } /// < summary> ///стратегия: Read, Write (Чтение, запись) /// < /summary> public int Age { set { age = value; //Изменение статуса if (age < 7) status = Status.ребенок; else if (age < 17) status = Status.школьник; else if (age < 22) status = Status.студент; else if (age < 65) status = Status.работник; else status = Status.пенсионер; } get { return (age); } } /// < summary> ///стратегия: Write-only (Только запись) /// < /summary> public int Salary { set { salary = value; } }}Рассмотрим теперь общий синтаксис методов-свойств. Пусть name - это закрытое свойство. Тогда для него можно определить открытый метод -свойство (функцию), возвращающую тот же тип, что и поле name. Имя метода обычно близко к имени поля, отличаясь от него, например, только заглавной буквой (Name). Тело свойства содержит два метода - get и set, один из которых может быть опущен. Метод get возвращает значение закрытого поля, метод set устанавливает значение, используя значение, передаваемое ему в момент вызова и хранящееся в служебной переменной со стандартным именем value. Поскольку get и set - это обычные процедуры языка, программно можно реализовать сколь угодно сложные стратегии доступа. В нашем примере фамилия меняется, только если ее значение равно пустой строке, и это означает, что фамилия персоны ни разу еще не задавалась. Статус персоны пересчитывается автоматически при всяком изменении возраста, явно изменять его нельзя. Заметьте, клиент работает с методами-свойствами так, словно они являются настоящими полями, вызывая их как в правой, так и в левой части оператора присваивания. Заметьте также, что с каждым полем можно работать только в полном соответствии с той стратегией, которую реализует данное свойство. Попытка изменения фамилии не принесет успеха, а изменение возраста приведет и к одновременному изменению статуса. На применение модификаторов доступа в аксессорах накладываются следующие ограничения: · Действию модификатора доступа подлежит только один аксессор: set или get, но не оба сразу. · Модификатор должен обеспечивать более ограниченный доступ к аксессору, чем доступ на уровне свойства или индексатора. · Модификатор доступа нельзя использовать при объявлении аксессора в интерфейсе или же при реализации аксессора, указываемого в интерфейсе. Авто свойства Если публичное свойство является всего лишь оберткой над приватной переменной и никакой логики не содержит, то достаточно задекларировать его точно таким же образом, как это делается в абстрактном классе, до всего остального компилятор додумается сам. Например, так: public class Animal{ public int Id { get; set; } public string Name { get; set; } public bool CanFly { get; set; }} Снаружи, для всех метаданных, это выглядит как обычное свойство, изнутри класса обращаться к нему тоже надо как к свойству. В общем, скомпилируется это дело в обычное свойство, в виде обертки над некоей приватной переменной. Естественно, как только мы захотим добавить какую-нибудь логику, тело свойства придется реализовать. Если же мы хотим, чтобы свойство было доступно извне только для чтения, то, поскольку просто опустить в объявлении set нельзя, синтаксис будет следующим: public int Id { get; private set; } Индексаторы Еще одним частным случаем является индексатор. Метод- индексатор является обобщением метода-свойства. Он обеспечивает доступ к закрытому полю, представляющему массив. Объекты класса индексируются по этому полю. Синтаксически объявление индексатора такое же, как и в случае свойств, но методы get и set приобретают аргументы по числу размерности массива, задающего индексы элемента, значение которого читается или обновляется. Важным ограничением является то, что у класса может быть только один индексатор и у этого индексатора стандартное имя this. Так что если среди полей класса есть несколько массивов, то индексация объектов может быть выполнена только по одному из них. Имя у индексатора this, в квадратных скобках в заголовке перечисляются индексы. В методах get, set анализируется корректность задания индекса. На применение индексаторов накладываются два существенных ограничения. Во-первых, значение, выдаваемое индексатором, нельзя передавать методу в качестве параметра ref или out, поскольку в индексаторе не определено место в памяти для его хранения. И во-вторых, индексатор должен быть членом своего класса и поэтому не может быть объявлен как static. Индексатор может быть перегружен. В этом случае для выполнения выбирается тот вариант индексатора, в котором точнее соблюдается соответствие его параметра и аргумента, указываемого в качестве индекса. Операции Еще одним частным случаем являются методы, задающие над объектами классами бинарную или унарную операцию. Введение в класс таких методов позволяет строить выражения, аналогичные арифметическим и булевым выражениям с обычно применяемыми знаками операций и сохранением приоритетов операций. В следующей таблице описаны возможности перегрузки основных операций:
Перегрузка операторов тесно связана с перегрузкой методов. Для перегрузки оператора служит ключевое слово operator, определяющее операторный метод, который, в свою очередь, определяет действие оператора относительно своего класса. Существуют две формы операторных методов (operator): одна — для унарных операторов, другая — для бинарных. Ниже приведена общая форма для каждой разновидности этих методов: public static возвращаемый_тип operator op(тип_параметра операнд) { // операции } // Общая форма перегрузки бинарного оператора. public static возвращаемый_тип operator ор(тип_параметра1 операнд1, тип_параметра2 операнд2) { // операции }
Здесь вместо ор подставляется перегружаемый оператор, например + или /, а возвращаемый_тип обозначает конкретный тип значения, возвращаемого указанной операцией. Это значение может быть любого типа, но зачастую оно указывается такого же типа, как и у класса, для которого перегружается оператор. Такая корреляция упрощает применение перегружаемых операторов в выражениях. Для унарных операторов операнд обозначает передаваемый операнд, а для бинарных операторов то же самое обозначают операнд1 и операнд2. Обратите внимание на то, что операторные методы должны иметь оба спецификатора типа - public и static.
Обратите внимание, что на перегрузку операторов отношения накладывается следующее важное ограничение: они должны перегружаться попарно. Так, если перегружается оператор <, то следует перегрузить и оператор >, и наоборот. Ниже приведены составленные в пары перегружаемые операторы отношения: ! = == < > < = > = И еще одно замечание: если перегружаются операторы == и! =, то для этого обычно требуется также переопределить методы Object.Equals() и Object.GetHashCode().
|