В .NET Framework все это учитывается. Почленное копирование простых объектов достигается за счет использования метода MemberwiseClone(), который наследуется от System.Object. Данный метод является защищенным, однако не составляет никакого труда определить общий метод на объекте, который его использует. Копирование, которое обеспечивается этим методом, известно под названием неглубокого копирования – в том смысле, что при его осуществлении не принимаются во внимание те члены, которые представляют собой ссылочные типы. Это означает, что ссылочные члены во вновь создаваемом объекте будут ссылаться на те же самые объекты, на которые ссылаются соответствующие члены в исходном объекте: во многих случаях это не совсем идеальный вариант. Если нам требуется создать новые экземпляры рассматриваемых полей и скопировать туда значения вместо ссылок, то мы должны выполнить так называемое глубокое копирование.
Существует интерфейс, который мы можем реализовать и который позволяет выполнить эту процедуру стандартным образом: ICloneable. Если мы собираемся его применить, нам необходимо реализовать единственный метод, содержащийся в нем,– Clone(). Этот метод возвращает значение типа System.Object. Мы можем использовать совершенно произвольные способы обработки для получения этого объекта, реализуя тело этого метода так, как нам требуется. Другими словами, мы можем реализовывать процедуру глубокого копирования в зависимости от нашего желания (например, мы при необходимости можем реализовать и процедуру неглубокого копирования).
6.5. Вопросы для повторения
1. Определение классов в C#.
2. Модификаторы доступа к членам класса.
3. Базовый класс System.Object.
4. Последовательность выполнения конструкторов.
5. Неглубокое и глубокое копирование.
В данной главе мы продолжим обсуждение способов определения классов в С#. Теперь мы переходим к рассмотрению того, что необходимо для определения членов классов, а именно полей, свойств и методов.
Мы начнем с того, что познакомимся с кодом, требующимся для определения каждого из этих членов, а также рассмотрим создание необходимой структуры кода с помощью соответствующего мастера (wizard) VS. Мы также узнаем, каким образом можно быстро изменять члены, редактируя их свойства.
После того как вы познакомитесь с основами определения членов, мы перейдем к рассмотрению более сложных способов их использования: перевода членов базового класса в скрытое состояние, обращения к переопределенным членам базового класса и вложенных определений типов.
В заключение мы применим эту теорию на практике и создадим библиотеку классов, которую можно будет постепенно достраивать и использовать в последующих главах.
7. 1.Члены классов
7.1.1.Определение членов
В рамках определения класса должны находиться определения всех членов данного класса, включая поля, методы и свойства. У каждого члена должен быть свой уровень доступа, который во всех случаях описывается одним из следующих ключевых слов:
- public (общий) – член, доступный из любого кода;
- private (частный) – член, доступный только из того кода, который является составной частью данного класса (значение по умолчанию, которое используется в случае отсутствия ключевого слова);
- internal (внутренний) – член, доступный только из кода, находящегося внутри проекта (модуля), в котором он определен;
- protected (защищенный) – член, доступный только из кода, являющегося составной частью данного класса или класса, производного от него.
Последние два ключевых слова могут использоваться совместно, т е. член можно описать как protected internal. Такие члены доступны только из производных классов, описанных в рамках проекта (или, точнее, модуля; модули будут рассматриваться в главе 21).
Поля, методы и свойства могут быть описаны также посредством ключевого слова static (статические): в этом случае они будут статическими членами данного класса, а не экземпляров объектов, что подробно обсуждалось в главе 8.
7.1.2.Определение полей
Поля определяются с помощью обычного формата объявления переменной (с дополнительной возможностью инициализации) и с использованием модификаторов, обсуждавшихся выше. Например:
class MyClass { public int MyInt; }
Для имен общих полей в .NET Framework применяется система регистров PascalCasing, а не camelCasing, и в данной книге будет использоваться такой же подход. Именно поэтому поле, описанное выше, названо MyInt, а не myInt. Это всего лишь рекомендуемая схема использования регистров, но она кажется автору наиболее осмысленной.
Для частных полей не существует никаких рекомендаций, такие поля обычно именуются с использованием системы camelCasing.
Для полей также может использоваться ключевое слово readonly (только чтение), что означает, что значение этого поля может быть задано только в процессе выполнения конструктора или при начальном присваивании. Например:
class MyClass { public readonly int MyInt = 17; }
Как было отмечено во введении к этой главе, поля могут объявляться статическими с помощью ключевого слова static. Например:
class MyClass { public static int MyInt; }
Доступ к статическим полям может осуществляться через класс, в котором они описаны (для вышеприведенного примера –MyClass.MyInt), но не через экземпляры объектов данного класса.
Кроме всего этого, существует возможность использования ключевого слова const (константа). Члены, описанные как const, являются статическими по определению, поэтому в данном случае модификатор static не требуется (более того, его использование является ошибочным).
7.1.3.Определение методов
Для описания методов используется стандартный формат функций и необязательный модификатор static. Например:
class MyClass { public string GetString()
{ return "Это строка."; } }
При именовании методов в .NET Framework, как и при именовании полей, используется система PascalCasing, а не camelCasing.
Заметьте, что если при описании метода использовано ключевое слово static, то этот метод будет доступен только через класс, но не через экземпляр объекта. При определении метода также можно использовать следующие ключевые слова:
- virtual (виртуальный) – метод может быть переопределен;
- abstract (абстрактный) – метод должен быть переопределен (допускается только в абстрактных классах); - override (переопределить) – метод переопределяет метод базового класса (это ключевое слово должно использоваться в том случае, если метод переопределяется);
- extern (внешний) – определение метода находится где-либо в другом месте. Следующий пример демонстрирует переопределение метода.
public class MyBaseClass { | ||||
public virtual void DoSomething() | ||||
{ // Базовая реализация } } | ||||
public class MyDenvedClass : MyBaseClass { | ||||
public override void DoSomething() { | ||||
// Реализация метода в производном классе, | ||||
// она переопределяет базовую реализацию } } |
При использовании модификатора override может применяться также модификатор sealed, который указывает на то, что никаких дальнейших модификаций этого метода в производных классах не допускается, т. е метод не может переопределяться в производных классах. Например: public class MyDerivedClass : MyBaseClass { public override sealed void DoSomething() { // Реализация метода в производном классе,
// она переопределяет базовую реализацию } }
Использование модификатора extern позволяет использовать реализацию метода, внешнюю по отношению к данному проекту. Это сложная тема, и мы здесь не будем в нее углубляться.
7.1.4.Определение свойств
Свойства определяются аналогично полям, но с ними все оказывается не так просто. Как обсуждалось ранее, свойства сложнее полей, поскольку они допускают дополнительную обработку перед изменением своего состояния. Это достигается за счет того, что они обладают двумя блоками, напоминающими функции, один из которых предназначается для получения значения свойства, а второй – для задания значения свойства.
Оба этих блока, определяемых посредством ключевых слов get (получи) и set (установи), могут использоваться для управления уровнем доступа к данному свойству. Существует возможность опустить тот или иной блок для создания свойства, которое будет использоваться в режиме "только чтение" или "только запись" (причем отсутствие блока get определяет "только запись", а отсутствие блока set определяет режим доступа "только чтение"). Это все, естественно, применимо только ко внешнему коду, поскольку любой код, находящийся внутри класса, будет иметь доступ ко всем тем данным, к которым имеет доступ код, находящийся в этих блоках. Для того чтобы получить корректное свойство, необходимо включить в его описание хотя бы один из этих двух блоков (свойство, которое нельзя ни считать, ни изменить, едва ли может принести хоть какую-нибудь пользу).
Основная структура свойства включает стандартное ключевое слово – модификатор доступа (public, private и т. д.), за которым следует имя типа, имя свойства и один или оба блока set и get, в которых содержится код обработки свойства.
Например:
public int MyIntProp { get
{ // Код для получения значения свойства } set
{ // Код для задания значения свойства } }
Имена общих свойств в .NET Framework также строятся на основе регистров PascalCasing, а не camelCasing, и в этой книге мы будем использовать для них – так же, как для полей и методов – именно эту систему.
Первая строка этого определения очень напоминает определение поля. Отличие заключается в том, что в конце строки отсутствует точка с запятой, вместо нее там размещаются вложенные блоки set и get.
Блоки get должны обладать возвращаемым значением, имеющим тот же тип, что и само свойство. Простые свойства очень часто привязывают к единственному частному полю, которое управляет доступом к этому свойству; в этом случае блок get может возвращать значение этого поля напрямую. Например:
Поле, используемое свойством private int myInt; | |||
Свойство | |||
public int MyIntProp | |||
get { return myInt; } | |||
set { // Код для задания свойства } | |||
//