Вторник, 16.04.2024, 13:41
Приветствую Вас Гость | RSS

Лекции

Меню сайта
Форма входа
Категории раздела
ТАУ (Теория автоматического управления) [31]
лекции по ТАУ
Экология [151]
учебник
Бухгалтерский учет и налогообложение в строительстве [56]
Дементьев А.Ю. - Практическое пособие
Психология [104]
Пип
информатика [80]
с# Карли Ватсон
современные стулья [0]
новинки
Поиск

Главная » 2010 » Февраль » 11 » Определение членов классов
00:23
Определение членов классов
Определение членов классов
В данной главе мы продолжим обсуждение способов определения классов
в С#. Теперь мы переходим к рассмотрению того, что необходимо для определения
членов классов, а именно полей, свойств и методов.
Мы начнем с того, что познакомимся с кодом, требующимся для определения
каждого из этих членов, а также рассмотрим создание необходимой структуры кода
с помощью соответствующего мастера (wizard) VS. Мы также узнаем, каким об-
разом можно быстро изменять члены, редактируя их свойства.
После того как вы познакомитесь с основами определения членов, мы перей-
дем к рассмотрению более сложных способов их использования: перевода членов
базового класса в скрытое состояние, обращения к переопределенным членам
базового класса и вложенных определений типов.
В заключение мы применим эту теорию на практике и создадим библиотеку
классов, которую можно будет постепенно достраивать и использовать в последу-
ющих главах.
Определение членов
В рамках определения класса должны находиться определения всех членов
данного класса, включая поля, методы и свойства. У каждого члена должен быть
свой уровень доступа, который во всех случаях описывается одним из следующих
ключевых слов:
• public (общий) — член, доступный из любого кода.
• private (частный) — член, доступный только из того кода, который
является составной частью данного класса (значение по умолчанию,
которое используется в случае отсутствия ключевого слова).
• internal (внутренний) — член, доступный только из кода,
находящегося внутри проекта (модуля), в котором он определен.
• protected (защищенный) — член, доступный только из кода,
являющегося составной частью данного класса или класса,
производного от него.
Последние два ключевых слова могут использоваться совместно, т. е. член можно
описать как protected internal. Такие члены доступны только из производных
классов, описанных в рамках проекта (или, точнее, модуля; модули будут рассмат-
риваться в гл$ве 21).
Поля, методы и свойства могут быть описаны также посредством ключевого
слова s t a t i c (статические): в этом случае они будут статическими членами данного
класса, а не экземпляров объектов, что подробно обсуждалось в главе 8.
Определение членов классов 205
Определение полей
Поля определяются с помощью обычного формата объявления переменной (с до-
полнительной возможностью инициализации) и с использованием модификаторов,
обсуждавшихся выше. Например:
class MyClass
public int Mylnt;
}
Для имен общих полей в .NET Framework применяется
система регистров PascalCasing, а не camelCasing,
и в данной книге будет использоваться такой же подход.
Именно поэтому поле, описанное выше, названо Mylnt,
а не myint. Это всего лишь рекомендуемая схема использования
регистров, но она кажется автору наиболее осмысленной.
Для частных полей не существует никаких рекомендаций,
такие поля обычно именуются с использованием
системы camelCasing.
Для полей также может использоваться ключевое слово readonly (только чте-
ние), что означает, что значение этого поля может быть задано только в процессе
выполнения конструктора или при начальном присваивании. Например:
class MyClass
{ . .. ...........
public readonly int Mylnt = 17;
>
Как было отмечено во введении к этой главе, поля могут объявляться статиче-
скими с помощью ключевого слова static. Например:
class MyClass
{
public static int Mylnt;
}
Доступ к статическим полям может осуществляться через класс, в котором они
описаны (для вышеприведенного примера — MyClass.Myint), но не через экземп-
ляры объектов данного класса.
Кроме всего этого, существует возможность использования ключевого слова const
(константа). Члены, описанные как const, являются статическими по определению,
поэтому в данном случае модификатор s t a t ic не требуется (более того, его исполь-
зование является ошибочным).
Определение методов
Для описания методов используется стандартный формат функций и необяза-
тельный модификатор static. Например:
class MyClass
public string GetStringO
{
return "Это строка.*;
206 Глава 10
При именовании методов в .NET Framework, как и при
именовании полей, используется система PascalCasing,
а не camelCasing.
Заметьте, что если при описании метода использовано ключевое слово static,
то этот метод будет доступен только через класс, но не через экземпляр объекта.
При определении метода также можно использовать следующие ключевые слова:
Q virtual (виртуальный) — метод может быть переопределен.
• abstract (абстрактный) — метод должен быть переопределен
(допускается только в абстрактных классах).
• override (переопределить) — метод переопределяет
метод базового класса (это ключевое слово должно
использоваться в том случае, если метод переопределяется).
• extern (внешний) — определение метода находится
где-либо в другом месте.
Следующий пример демонстрирует переопределение метода:
.-. ' . .; : '. '• public class MyBaseClass . .-
{ * ' ^ ' : \ : : ' S ' - ' • • : ' ; ^ • • • • • • ' .
public virtual void DoSomethingO
{
• //Базовая реализация
public class MyDerivedCiass : MyBaseClass
{ • .' . • • : .
public override void DoSomethingO .
\ . ' I • i . ' • .
//• Реализация метода в производном классе,
// ока переопределяет базовую реализацию
При использовании модификатора override может применяться также модифи-
катор sealed, который указывает на то, что никаких дальнейших модификаций этого
метода в производных классах не допускается, т. е. метод не может переопреде-
ляться в производных классах. Например:
public class MyDerivedCiass : MyBaseClass
{
public override sealed void DoSomethingO
{
// Реализация метода в производном классе,
II она переопределяет базовую реализацию
Использование модификатора extern позволяет использовать реализацию ме-
тода, внешнюю по отношению к данному проекту. Это сложная тема, и мы здесь
не будем в нее углубляться.
Определение свойств
Свойства определяются аналогично полям, но с ними все оказывается не так
просто. Как обсуждалось ранее, свойства сложнее полей, поскольку они допускают
дополнительную обработку перед изменением своего состояния. Это достигается
Определение членов классов 207
за счет того, что они обладают двумя блоками, напоминающими функции, один из
которых предназначается для получения значения свойства, а второй — для зада-
ния значения свойства.
Оба этих блока, определяемых посредством ключевых слов get (получи) и set
(установи), могут использоваться для управления уровнем доступа к данному свой-
ству. Существует возможность опустить тот или иной блок для создания свойства,
которое будет использоваться в режиме "только чтение" или "только запись"
(причем отсутствие блока get определяет "только запись", а отсутствие блока set
определяет режим доступа "только чтение"). Это все, естественно, применимо
только ко внешнему коду, поскольку любой код, находящийся внутри класса, будет
иметь доступ ко всем тем данным, к которым имеет доступ код, находящийся
в этих блоках. Для того чтобы получить корректное свойство, необходимо вклю-
чить в его описание хотя бы один из этих двух блоков (свойство, которое нельзя ни
считать, ни изменить, едва ли может принести хоть какую-нибудь пользу).
Основная структура свойства включает стандартное ключевое слово — моди-
фикатор доступа (public, private и т. д.), за которым следует имя типа, имя свой-
ства и один или оба блока set и get, в которых содержится код обработки
свойства. Например:
public int MyIntProp -
get
{
// Код для получения значения свойства
set
{
II Код для задания значения свойства
Имена общих свойств в .NET Framework также строятся
на основе регистров PascalCasing, а не camelCasing,
и в этой книге мы будем использовать для них —
так же, как для полей и методов — именно эту систему.
Первая строка этого определения очень напоминает определение поля. Отли-
чие заключается в том, что в конце строки отсутствует точка с запятой; вместо нее
там размещаются вложенные блоки set и get.
Блоки get должны обладать возвращаемым значением, имеющим тот же тип,
что и само свойство. Простые свойства очень часто привязывают к единственному
частному полю, которое управляет доступом к этому свойству; в этом случае блок
get может возвращать значение этого поля напрямую. Например:
// Поле, используемое свойством
private int myInt;
// Свойство
public int MyIntProp
{
get
{
return mylnt;
}
set
{
// Код для задания свойства
208 Глава 10
Заметьте, что внешний код не будет иметь непосредственного доступа к полю
myint из-за его уровня доступа (оно описано как частное). Напротив, такой код
должен использовать свойство для того, чтобы получить доступ к полю.
Функция set присваивает полю значение аналогичным образом. В этом случае
можно воспользоваться ключевым словом value, чтобы сослаться на значение, по-
лученное от пользователя:
// Поле, используемое свойством
private int myInt;
// Свойство
public int MyintProp
{
get
{
return myint;
}
set
myint = value;
Значение value имеет такой же тип, что и свойство, поэтому, если используется
тот же тип, что и тип поля, не приходится беспокоиться о приведении типов.
Это простое свойство не только защищает поле myint от непосредственного до-
ступа. Истинная мощь свойств проявляется, когда требуется несколько больший
контроль над происходящим. Например, мы можем реализовать блок set следую-
щим образом:
set
if (value >- 0 && value <= 10)
myint = value;
}
В данном случае мы изменяем myint, только если значение свойства находится
между 0 и 10. В подобных ситуациях необходимо решить: что предпринять, если
использовано недопустимое значение? Здесь имеются четыре возможности:
• Ничего не предпринимать (как в коде, приведенном выше).
• Присвоить полю значение по умолчанию.
• Продолжать как ни в чем ни бывало, но зарегистрировать
событие для последующего анализа.
• Сгенерировать исключительную ситуацию.
Вообще говоря, предпочтительными являются две последние возможности,
а выбор между ними зависит от того, каким образом будет использоваться данный
класс и в какой степени следует передать контроль пользователям класса. Генери-
рование исключительной ситуации дает пользователям очень большие возможности
контроля, позволяет получать информацию о том, что происходит, и соответствен-
но реагировать. Для этого можно воспользоваться стандартной исключительной
ситуацией system, например:
set
{
if (value >= 0 && value <= 10)
myint = value;
Определение членов классов 209
else
throw (new ArgumentOutOfRangeException("MyIntProp", value,
"Значение свойства MylntProp должно лежать в
диапазоне между 0 и 10.*)) ;
}
Эту ситуацию можно обработать, применив конструкцию try.. .catch.. .finally
в коде, который использует данное свойство, как это было описано в главе 7.
Регистрация данных, возможно, в текстовом файле, может оказаться полезной
для производственных программ, в которых никаких проблем возникать не должно.
Эти данные позволяют разработчикам проверять работоспособность программ и,
при необходимости, исправлять обнаруженные ошибки.
Свойства, так же как и методы, могут использовать ключевые слова virtual,
override и abstract, т. е. те ключевые слова, использование которых для полей
оказывается недопустимым.
Практикум: использование полей, методов и свойств
1. Создайте новый проект консольного приложения с именем chiOExOi
В Директории C:\BegCSharp\ChapterlO.
2. С помощью VS добавьте новый класс с именем MyClass
В файл MyClass.cs.
3; Измените код в Myciass.cs следующим образом:
public class MyClass
private int intVal;
public int Val
s e t
if (value >= 0 && value <= 10)
intVal = value;
else
throw (new ArgumentOutOfRangeException("Val", value,
"Val must be assigned a value between 0 and 10."));
// Значение, присваиваемое Val, должно лежать в диапазоне между 0 и 10. //
public override string ToStringO
return "Name: " + Name + "\nVal: " + Val; :/:.
ШШшёШШтШШшШйшШШ
private MyClass() : this("Default Name")
.public MyClass(string newName)
Name = newName;
intVal = 0;
210 Глава 10
4. Измените код в ciassi.cs следующим образом:
static void Main(string[] args)
{ ^ , , , ." щшт
Console.WriteLine("Creating object myObj...");
MyClass myObj = new MyClass(*My Object*);
Console.WriteLine("myObj created.");
for (int i ~ -1; i <= 0; i++)
( .-у--' '• ':,'.
try
{
Console.WriteLine(*\nAttempting to assign {0} to myObj.Val...", i);
myObj.Val = i; .
Console.WriteLine("Value {0} assigned to myObj.Val.', myObj.Val);
}
catch (Exception e)
{
Console.WriteLine('Exception {0} thrown.*, e.GetTypeO.FullName);
Console.WriteLine("Message:\n\*{0}\"", e.Message);
Console.WriteLine("\nOutputting myObj.ToStringO...
Console.WriteLine (myObj .ToString());
Console.WriteLine("myObj.ToString() Output.*);
yU\Be«jrsh ^Detnui-X
eat in<£ object nyObj. » •
/Obj cheated.
$fctc;^pting to assign—1 to ft.yObJ.Ual..«
Exception S5l?steR.Йl^цйer^t0^tt0fRafigeException til
Hessage:
"Ual must fee assigned a walwe between 0 and 18.
Parapmtei» mane» Ual ,
Actual value was...-!,.*'
Attempting to assign 0 to rayObJ.Ual»..
Ualue Ш assigned to n^Obj.MaZ.
Output:t ing myObJ .ToSt^iing< >»«»
Nane; Нц Object
Ual: 8
ns?Obj.ToString<> Output.
Pi»ecs <my key to continue
5. Запустите приложение
(см. рис. слева).
Как это работает
Код в Main о создает и использует
экземпляр класса MyClass, который
определен в Myciass.cs. Создание эк-
земпляра данного класса должно осуще-
ствляться с помощью конструктора не
по умолчанию, поскольку конструктор
по умолчанию класса MyClass является
частным:
private MyClass() : this(-Default Name*)
Обратите внимание, что используется this ("Default Name") —для гарантиро-
ванного присвоения Name какого-либо значения при обращении к конструктору.
Последнее возможно, если данный класс используется для создания нового класса.
Отсутствие значения у поля Name в дальнейшем может привести к возникновению
ошибок.
Используемый не по умолчанию конструктор присваивает значения описанному
как readonly полю name (это присваивание можно осуществить либо при объявле-
нии поля, либо с помощью конструктора) и частному полю intvai.
Далее Main () пытается выполнить два присваивания свойству val объекта myOb j
(он является экземпляром класса MyClass). Для присваивания значений -1 и 0 ис-
пользуются два прохода цикла for, а для обнаружения возможных исключительных
ситуаций применяется конструкция try...catch. Когда свойству присваивается - 1 ,
ВОЗНИКаеТ ИСКЛЮЧИТеЛЬНаЯ Ситуация System.ArgumentOutOfRangeException И КОД,
находящийся в блоке catch, выводит информацию о ней в окно консоли. При
Определение членов классов 211
следующем проходе цикла свойству vai присваивается значение 0, а затем через
это свойство значение присваивается частному полю intvai.
В заключение для вывода отформатированной строки, представляющей содер-
жимое объекта, используется переопределенный метод Tostring ():
public override string ToStringO
{
return "Name: 1 + Name + "\nVal: • + Vai;
Этот метод должен быть объявлен с использованием ключевого слова override,
поскольку названный метод переопределяет виртуальный метод Tostring о базо-
вого класса System.object. Код в данном случае использует непосредственно свой-
ство vai, а не частное поле intvai. Нет никаких причин, по которым мы не могли
бы использовать свойства внутри класса подобным образом, хотя в этом случае
может возникать небольшое замедление работы программы (настолько небольшое,
что мы вряд ли сумеем его обнаружить). Использование свойства, кроме того, по-
зволяет осуществлять контроль за допустимостью значений, присущий использова-
нию свойств, что также может оказаться полезным для кода, находящегося внутри
класса.
Мастера VS для работы с членами
В предыдущей главе вы познакомились с использованием вспомогательных
программ в VS для создания нового класса, укомплектованного собственным
.cs-файлом. Кроме них, существует еще некоторое количество таких программ
(известных под названием мастеров), которые могут применяться для внесения
изменений в классы, а именно: мастера для добавления классам свойств, методов
и полей. К рассмотрению этих мастеров мы и приступаем.
Доступ ко всем мастерам для работы с чле-
нами осуществляется одним и тем же способом.
В окне Class View, в котором выводятся описан-
ные в проектах классы вместе со своими члена-
ми, необходимо щелкнуть правой кнопкой мыши
по соответствующему классу и выбрать из рас-
крывшегося меню опцию Add (см. рис. справа).
В этом меню имеется четыре возможности, но
мы в настоящий момент будем рассматривать
только три из них, поскольку индексаторы явля-
ются предметом изучения следующей главы.
Wetcome to the C# Add Method WIUM-CI
ТШ weard adds a tmetteti Ы w C# <&»»,
1 - Л Я Z
• • • • •
м« 1 «

! C.VK*J J
_ t ' 4 g f o w s e D e f i n i t i o n
. $ £ > Q u c k F i u d S y m b o l ;
S o r t B y A c c 2 , * $
1
j £ i A d d P r o p e r l y . . . " * !
v A d d F i e l d
' ( ] A e k J l n d e x e r . »
Мастер Add Method
(добавление методов)
Данный мастер — кто бы мог подумать! —
позволяет добавлять методы в класс. Это до-
стигается с помощью диалогового окна Method
Wizard, представленного на рисунке слева (на
рисунке некоторые его поля уже заполнены).
Названное окно обеспечивает доступ ко всем
возможностям методов. Мы получаем средст-
во, позволяющее изменять уровень доступа
212 Глава 10
с помощью раскрывающегося поля Method access (доступ к методу), тип возвра-
щаемого значения — с помощью раскрывающегося поля Return type (тип возвра-
щаемого значения; кроме того, существует возможность ввести в это поле наше
собственное название типа), имя — с помощью раскрывающегося поля Method
name (имя метода), а также другие модификаторы — с помощью Method modifiers
(модификаторы метода). Поле модификаторов метода допускает использование только
корректных сочетаний, и в абстрактных классах возможны только абстрактные
методы. Мы можем также добавлять параметры, используя для этой цели поля
Parameter type (тип параметра) и Parameter name (имя параметра). Поле Modifier
(модификатор) позволяет задавать тип параметра: обычный (None), ref или out.
Добавленные параметры появляются в поле Parameter list (список параметров),
когда мы нажимаем кнопку Add (добавить); исключать параметры из списка можно
с помощью кнопки Remove (удалить).
Это диалоговое окно позволяет ввести комментарий, помещаемый перед опре-
делением метода, в поле Comment (комментарий), а также посмотреть код, кото-
рый будет сгенерирован, в окне Method signature (сигнатура метода).
Нажатие кнопки Finish (завершение) при тех установках, которые изображены
на рисунке, приведет к добавлению следующего кода в наш класс:
public double myMethod(double paramX, double ParamY)
return 0;
Совершенно очевидно, что мастер оказывается не в состоянии реализовать сам
метод за нас, однако он создает базовую структуру метода и, безусловно, сущест-
венно снижает количество ошибок при вводе текста!
Мастер Add Property (добавление свойств)
Мастер Add Property использует диало-
говое окно, показанное на рисунке слева
(некоторые поля нами уже заполнены).
Мы выбираем тип доступа с помощью
раскрывающегося поля Property access
(доступ к свойству), тип свойства — с по-
мощью поля Property type (тип свойства),
вводим имя в поле Property name (имя
свойства), определяем, какие блоки нам
необходимы — get, set или одновременно
и get, и set, а также по желанию добав-
ляем модификаторы и комментарии. На-
жатие кнопки Finish (завершение) при
изображенных на рисунке установках со-
здает следующий код:
public static int mylnt
{ fill • ; .
get
k& Prcpcrty Wteatt) * -'СЫОЫ»
welcome to the C# Add -Property wtearcf
т*у, wae^tj «Ids a proporiy to; your c * «Sws.
- - •
d (S3
Щ
>{•;;
return 0;
Определение членов классов 213
Обратите внимание, что окончательная реализация свойства возлагается на нас
(в случае простых свойств она сводится к установлению соответствия между свой-
ством и полем). Однако теперь имеется возможность использовать базовую струк-
туру свойства.
Мастер Add Field
(добавление поля)
Наконец, рассмотрим мастер Add Field
(см. рис. справа). Это диалоговое окно
оказывается еще проще.
Оно в основном схоже с диалоговым
окном для добавления свойств. Исключение
составляет отсутствие возможности выби-
рать блоки get и set и использовать клю-
чевые СЛОВа virtual И abstract. Однако
можно определить поле как константу
и воспользоваться Field value (значение
поля) для задания его значения.
Код при этом генерируется простой
и самоописательный:
to the С# Add field WtMfdT
TNs Ыця& adds 4 f^d IQ yog? C# dsss.
[ £*«•«<•
Г'ЭДЦС
private double myDouble;
j my Method CodeFunction j*j
(Kerne)
1 o:ess
CanOverride
IsGveHvoc&d
IsShared
myMethod
^pub!.c
P«be
Ch 1 Dc>-02 .CI-35S2 .myMcthod
Fefse |:i
False
j Access
• Defines the access attributes of this item.
f£p Properties [ 0 0/namic Hep I
Свойства членов
Последняя из основных тем, которую необходимо рас-
смотреть,— изменение свойств членов с помощью окна
Properties. После того как мы выбрали член в окне Class
View, мы получаем возможность просматривать его свойст-
ва в окне Properties (см. рис. слева).
Из этого окна мы можем непосредственно* изменять
многие Свойства, например, уровень доступности, посредст-
вом свойства Access (доступ) (оно выделено на рисунке).
В этом случае код будет модифицирован автоматически —
и не придется ничего набирать на клавиатуре.
Обратите внимание, что свойство CanOverride (может
переопределяться) позволяет определить, является ли член
виртуальным, a IsShared (с разделением доступа) — явля-
ется ли он статическим.
Некоторые дополнительные темы,
касающиеся членов класса
Теперь, когда вы познакомились с основами определения членов, самое время
перейти к рассмотрению более сложных тем. Здесь будут рассмотрены следующие
из них:
• Сокрытие методов базового класса
• Вызов переопределенных или скрытых методов базового класса
• Вложенные определения типов
214 Глава 10
Сокрытие методов базового класса
При наследовании некоторого (не абстрактного) члена базового класса насле-
дуется также и его реализация. Если наследуемый член является виртуальным,
то имеется возможность переопределить его реализацию, воспользовавшись
ключевым словом override. Независимо от того, является ли наследуемый член
виртуальным, при необходимости можно скрыть его реализацию. Это полезно, на-
пример, если наследуемый общий член работает не так, как требуется.
Сокрытие достигается использованием следующего кода:
. { . • • ' • • •• .• • ••: ..
I . : ; ; : :•: У У- . ' : . .
У У • У
1ШШ ' \ ЯШШШ! Шт 'У:ШШ^
E : - : ^ " y '••••'••
public void DoSomethingO
{
// Реализация в производном классе, скрывающая базовую реализацию
Хотя такой код будет работать нормально, он сгенерирует предупреждение о том,
что в нем скрывается член базового класса. Это дает возможность исправить по-
ложение дел, если мы случайно скрыли член, который на самом деле желаем ис-
пользовать. Если же этот член действительно требуется скрыть, то об этом можно
сказать явно, воспользовавшись ключевым словом new:
public class MyDerivedClass : MyBaseClass
{
new public void DoSomethingO
{
// Реализация в производном классе, скрывающая базовую реализацию
Этот код будет работать точно так же, только без выдачи предупреждения.
На данном этапе нам представляется важным отметить, в чем заключается раз-
ница между скрытыми и переопределенными членами базового класса. Рассмот-
рим следующий код:
public class MyBaseClass
{ . . .. .
public virtual void DoSomethingO
{
Console.WriteLine("Базовая реализация");
}
}
public class MyDerivedClass : MyBaseClass
{
public override void DoSomethingO
{
Console.WriteLine('Производная реализация");
Определение членов классов 215
В данном случае переопределенный метод заменяет собой реализацию базового
класса, так что в следующем коде будет использоваться новая версия, несмотря на
то, что обращение к методу происходит через тип базового класса (с использова-
нием полиморфизма):
MyDerivedClass myObj - new MyDerivedClass();
MyBaseClass myBaseObj;
myBaseObj = myObj; •
myBaseObj.DoSomething();
В данном случае выходной поток будет следующим:
Производная реализация
В качестве альтернативы мы можем скрыть метод базового класса с помощью
следующего кода:
public class MyBaseClass
{
public virtual void DoSomething()
{
Console.WriteLine('Базовая реализация") ;
}
}
public class MyDerivedClass : MyBaseClass
{ .. : >:. ............
new public void DoSomething ()
{
Console.WriteLine("Производная реализация") ;
Метод базового класса при этом совершенно не обязательно должен быть вир-
туальным, однако от этого ничего не изменится, и приведенный здесь код отлича-
ется от предыдущего варианта только одной строкой. Результат выполнения этого
кода как при виртуальном методе, так и в противном случае будет следующим:
Базовая реализация
Несмотря на то что базовая реализация является скрытой, к ней, как и раньше,
имеется доступ через базовый класс.
Вызов переопределенных или скрытых методов
базового класса
Независимо от того, переопределяем ли мы некоторый член или делаем его
скрытым, по-прежнему остается возможность доступа к члену базового класса
внутри данного класса. Существует множество различных ситуаций, когда это ока-
зывается полезным, например:
• Если требуется скрыть наследуемый общий член
от пользователей производного класса, но необходимо иметь
доступ к его функциональным возможностям внутри класса.
• Если требуется что-либо добавить к реализации
наследуемого виртуального члена, а не просто заменить ее
на новую переопределенную реализацию.
Для достижения подобного эффекта можно воспользоваться ключевым словом
base, которое указывает на реализацию базового класса, содержащуюся внутри
216 Глава 10
производного класса (аналогично тому, как оно используется в управляющих кон-
структорах, с которыми вы познакомились в предыдущей главе). Например:
public class MyBaseClass
{
public virtual void DoSomething()
{
// Базовая реализация
• Г Г • •:•'" • . • • . . .' .
public class MyDerivedClass : MyBaseClass
{
public override void DoSomething()
{
// Реализация в производном классе, расширяющая реализацию
в базовом классе base.DoSomething();
//Дополнительная реализация в производном классе
В представленном коде выполняется версия DoSomething о , содержащаяся в клас-
се MyBaseClass (базовом ПО Отношению К MyDerivedClass), ИЗ КОТОрОЙ вызывается
версия DoSomething (), содержащаяся внутри класса MyDerivedClass.
Поскольку ключевое слово base работает с экземплярами объектов, то будет
ошибкой использовать его внутри статического члена.
Ключевое слово this
Кроме ключевого слова base, в предыдущей главе мы также использовали клю-
чевое слово this. Так же, как и base, this может применяться внутри членов клас-
са, и так же, как и base, оно относится к экземпляру объекта. Экземпляр объекта,
на который указывает this, является текущим экземпляром объекта (отсюда сле-
дует, что это ключевое слово нельзя использовать в статических членах, поскольку
они не являются составной частью экземпляра объекта).
Наиболее ценной функцией ключевого слова this является возможность пере-
давать некоторому методу ссылку на текущий экземпляр объекта, например:
public void doSomething() : : :
{
MyTargetClass myObj = new MyTargetClass();
myObj.doSomethingWith(this);
}
В данном примере класс MyTargetClass, экземпляр которого создается, обладает
методом DoSomethingwithO. Ему передается единственный параметр, имеющий
тип, совместимый с тем классом, в котором содержится этот метод. Тип параметра
может совпадать с типом данного класса, с типом класса, наследником которого
является данный класс, может являться интерфейсом, реализованном в данном
классе, или же (естественно) быть типом system.object.
Вложенные определения типов
Помимо описания типов как классов в пространствах имен их можно описывать
внутри других классов. В таком случае появляется возможность использовать
в определениях весь спектр модификаторов доступности, а не только public и
internal, а также "применять ключевое слово new для перевода определения
типа, наследуемого от базового класса, в скрытое состояние.
Определение членов классов 217
В следующем коде, кроме класса MyClass, описывается также вложенный класс
С именем myNestedClass:
public class MyClass
{
public class myNestedClass
{
public int nestedClassField;
ш i
Если потребуется создать экземпляр класса myNestedClass откуда-нибудь извне
класса MyClass, то его имя должно квалифицироваться:
MyClass.myNestedClass myObj = new MyClass.myNestedClass();
Однако вполне возможно, что мы окажемся не в состоянии выполнить такие
действия, если вложенный класс объявлен как частный или обладает каким-либо
другим уровнем доступности, несовместимым с кодом в той точке, в которой осу-
ществляется попытка создания соответствующего экземпляра.
Основной причиной этого является необходимость описания классов, которые
являются частными по отношению к содержащему их классу, так что никакой дру-
гой код в данном пространстве имен не будет иметь к ним доступа.
Реализация интерфейсов
Перед тем как двигаться дальше, имеет смысл более подробно рассмотреть
способы определения и реализации интерфейсов. В предшествующей главе вы уз-
нали, что интерфейсы определяются аналогично классам, для чего используется
код следующего вида:
interface IMylnterface
{
// члены интерфейса
}
Члены интерфейса определяются так же, как и члены класса, за исключением
нескольких существенных отличий:
• Не допускается использование никаких модификаторов доступности
(public, private, protected И internal) — все члены интерфейса
неявно общие.
• Члены интерфейса не могут содержать тела программы.
• Интерфейсы не могут описывать члены в виде полей.
• При описании членов интерфейса не могут использоваться
ключевые СЛОВа static, virtual, abstract И sealed.
• Члены описания типов не допускаются.
Существует, однако, возможность использовать при описании членов ключевое
слово new в тех случаях, когда требуется перевести в скрытое состояние члены,
наследуемые от базовых интерфейсов. Например:
interface IMyBaselnterface
{
void DoSomething();
218 Глава 10
interface IMyDerivedlnterface : IMyBaselnterface
{
new void DoSomething();
}
Это работает точно таким же образом, как и перевод в скрытое состояние насле-
дуемых членов класса.
В свойствах, определяемых в интерфейсах, задается, какие блоки доступа —
get и/или set — допустимы для данного свойства; например:
interface IMylnterface
.—jyi^ , . „ ,
int My Int
get ;
set; :
В данном случае свойство Myint типа int обладает обоими средствами доступа —
и get, и set. Любое из них может быть опущено для свойства, доступ к которому
является более ограниченным.
Однако обратите внимание на то, что интерфейсы не определяют, каким имен-
но образом должно храниться свойство. Они не могут указать, например, поля, ко-
торые использовались бы для хранения данных, определяющих свойство.
Наконец, интерфейсы, как и классы, могут определяться как члены классов
(но не как члены других интерфейсов, поскольку интерфейсы не могут содержать
в себе определения типов).
Реализация интерфейсов в классах
Класс, в котором реализован интерфейс, должен содержать в себе реализации
всех членов данного интерфейса, которые, в свою очередь, должны соответство-
вать указанной сигнатуре (включая соответствие указанным блокам get и set)
и быть общими. При реализации членов интерфейса допускается использование
ключевых слов virtual и abstract, но не s t a t i c или const. Например:
public interface IMylnterface
{ ; • ; v : V " v - : : : : . : •;••••••:••.••:••;•• > • - . . . .
void DoSomething () ;
void DoSomethingElseO ;
}
public class MyClass : IMylnterface
(
public void IMylnterface.DoSomething()
{
public void DoSomethingElseO
}
Интерфейсы также могут реализовываться на базовых классах, например:
public interface IMylnterface
{
void DoSomething () ;
void DoSomethingElse();
Определение членов классов 219
public class MyBaseClass
{ /. .. . ' ;•'. •'
public void DoSomething()
public class MyDerivedClass : MyBaseClass,: IMylnterface
: • ••: { ' •• . . . . . , . . / . .; . . . .. . . • :
:' ' ' .
-.:••••••••••. p u b l i c v o i d D o S o m e t h i n g E l s e ()
>
у Щ I Wm
Наследование из базового класса, который реализует данный интерфейс, озна-
чает, что этот интерфейс опосредованно поддерживается и производным классом:
public interface IMylnterface
{
void DoSomething () ;
void DoSomethingElse();
}
public class MyBaseClass : IMylnterface
{
public virtual void DoSomething()
( . • : •'•••• \ Ж ;::•' t l •
public virtual void DoSomethingElse()
{
}
public class MyDerivedGlass : MyBaseClass
{
public override void DoSomething()
Как было показано выше, реализации в базовом классе полезно определять как
виртуальные — для того, чтобы производные классы могли заменять эти реализа-
ции, а не переводить их в скрытое состояние. Если мы переведем метод в скрытое
состояние с помощью ключевого слова new, вместо того чтобы переопределить его
Таким Образом, ТО В ЭТОМ Случае метод IMylnterface.DoSomething() будет всегда
ссылаться на версию, описанную в базовом классе, даже если с помощью этого
интерфейса осуществляется доступ к производному классу.
Явная реализация членов интерфейса
Члены интерфейса также могут реализовываться в классе явным образом.
В этом случае доступ к членам осуществляется только через интерфейс, но не че-
рез класс. Доступ к неявным членам, т. е. к тем, которые мы использовали в коде
в предыдущем разделе, может осуществляться обоими способами.
Например, еСЛИ В Классе MyClass метод DoSomething О интерфейса IMylnterface
реализован неявным образом, следующий код является допустимым:
MyClass myObj = new MyClass();
myObj,DoSomething();
220 Глава 10
Как и такой:
MyClass myObj = new MyClass();
IMylnterf ace mylnt = myObj ;
mylnt.DoSomething();
Напротив, В ТОМ случае, еСЛИ В классе MyDerivedClass МеТОД DoSomething()
реализован явным образом, то разрешается использовать только второй способ.
Для этого потребуется записать следующий код:
public class MyClass : IMyInterface
{
void IMylnterf ace. DoSomething {•). :
• ^ ^ { •••••:
public void DoSomethingElse ()
Щ : {
ШЛ ••;• •'.:}, ) Д Ш Ж Л " ' : - }
ШштШ$. ШШ • Щ У
Здесь DoSomething () реализован ЯВНЫМ Образом, a DoSomethingElse () — неявным.
Непосредственный доступ через экземпляр объекта класса MyClass допускается
только ко второму из них.
Пример приложения
/ Для того чтобы проиллюстрировать некоторые приемы, описывавшиеся нами
ранее, мы создадим модуль классов, который сможем достраивать и использовать
в последующих главах. Этот модуль будет содержать в себе два класса:
• Класс Card (карта) представляет собой стандартную игральную карту
какой-либо масти — треф, бубен, червей или пик — и определенного
старшинства в диапазоне между тузом и королем.
• Класс Deck (колода) представляет собой полную колоду из 52 карт,
обеспечивает возможность получать карту по ее местоположению
в колоде и позволяет перетасовывать карты.
Мы также разработаем простое клиентское приложение, чтобы убедиться, что
все это работает, но использовать колоду в полноценном приложении для карточ-
ных игр мы не будем — пока не будем!
Планирование приложения
Наши классы будут содержаться в библиотеке классов этого приложения —
chiocardLib. Прежде чем приступать к написанию какого бы то ни было кода, нам
необходимо спланировать необходимую структуру и функциональные возможности
наших классов.
Класс Card
Класс card в основном будет служить контейнером для двух полей, использую-
щихся в режиме "только чтение": suit (масть) и rank (старшинство). Причина, по
которой мы собираемся создавать поля, используемые в режиме "только чтение",
заключается в том, что нет смысла использовать "пустую" карту и не должно
Определение членов классов 221
Card
••suit
i-rank
«ToStringQ
1
Card
+suit
+rank
+ToString()
0..*
Deck
•cards: CardQ
+GetCard()
+Deck()
+Shuffle()
существовать возможности изменять карты после того, как они созданы. Для до-
стижения этой цели мы опишем конструктор по умолчанию как частный и введем
альтернативный конструктор, который будет создавать карту по заданным масти
и старшинству.
Кроме этого, в классе Card будет еще переопределен метод TostringO класса
System.object таким образом, чтобы можно было простым способом получать
удобоваримое строковое представление любой карты. Ради некоторого
упрощения задачи для обоих этих полей — suit и rank — мы опреде-
лим перечислимые типы.
Класс card может быть представлен, как показано на рисунке
справа."
Класс Deck
Класс Deck будет поддерживать 52 объекта Card. Для этого мы используем
тип простого массива. Непосредственного доступа к этому массиву предусмот-
рено не будет — доступ к объектам card будет осуществляться посредством
метода Getcardo (получение карты), который будет возвращать карту по за-
данному индексу.
Этот класс также должен обладать методом shuffle о (тасуй), который
позволяет расположить карты в массиве по-новому, поэтому его можно пред-
ставить, как показано на рисунке слева.
Создание библиотеки классов
Для целей настоящего примера авторы исходили из предположения, что вы уже
настолько хорошо знакомы с VS, что не нуждаетесь в стандартном "Практикуме",
поэтому явно последовательность необходимых действий не указана.
Оба класса, а также перечислимые типы будут содержаться в проекте по созда-
нию библиотеки классов с именем chiocardLib. Этот проект будет состоять из двух
.cs-файлов: Card.cs, в котором будет находится определение класса card вместе
с двумя перечислимыми типами suit и Rank, и Deck.cs, в котором будет находится
определение класса Deck.
Card.cs
В этом разделе мы подробно разберем и проработаем код для card.cs. В нача-
ле, как всегда, мы включим обычный оператор using и объявление пространства
имен:
using System; .•
namespace ChiOCardLib
Далее мы поместим определение перечислимого типа suit:
public enum Suit
Club,
Diamond,
Heart,
Spade
//трефа//
//бубна//
//черва//
//пика//
222 Глава 10
Теперь нам потребуется определение перечислимого типа Rank, которое для
простоты будет начинаться с представления базового типа для туза, равного 1:
public enum Rank
Асе = 1, //туз//
Deuce,
Three,
Four,
Five,
Six,
Seven,
Eight,
Nine,
Ten,
Jack,
Queen,
King
//двойка//
//тройка//
//четверка//
//пятерка//
//шестерка//
//семерка//
//восьмерка//
//девятка//
//десятка//
//валет/
//дама//
//король//
Оба этих типа являются общими, поскольку мы рассчи
Категория: информатика | Просмотров: 1496 | Добавил: basic | Рейтинг: 0.0/0
Всего комментариев: 0
Имя *:
Email *:
Код *:
Календарь
«  Февраль 2010  »
ПнВтСрЧтПтСбВс
1234567
891011121314
15161718192021
22232425262728
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0

krutoto.ucoz.ru
Бесплатный конструктор сайтов - uCoz