Дополнительные сведения о классах - 11 Февраля 2010 - Лекции
Вторник, 06.12.2016, 18:57
Приветствую Вас Гость | RSS

Лекции

Меню сайта
Форма входа
Категории раздела
ТАУ (Теория автоматического управления) [31]
лекции по ТАУ
Экология [151]
учебник
Бухгалтерский учет и налогообложение в строительстве [56]
Дементьев А.Ю. - Практическое пособие
Психология [104]
Пип
информатика [80]
с# Карли Ватсон
современные стулья [0]
новинки
Поиск
Главная » 2010 » Февраль » 11 » Дополнительные сведения о классах
00:26
Дополнительные сведения о классах
Дополнительные сведения
о классах
К настоящему моменту мы рассмотрели практически все основные приемы ООП,
использующиеся в С#; однако существует несколько более сложных способов,
с которыми также не мешает познакомиться. В этой главе будут обсуждаться сле-
дующие темы:
• Семейства — это объекты, в которых могут содержаться массивы
других объектов и которые обладают функциональными возможностями
для организации доступа к ним.
• Перегрузка операторов — процедура конфигурирования классов
с тем, чтобы иметь возможность применять такие операторы,
как, например, + , для выполнения операций над экземплярами
данного класса.
• Усложненное преобразование — некоторые дополнительные
возможности по преобразованию типов в С#.
• Глубокое копирование — это процедура копирования, гарантирующая,
что создаваемые объекты не содержат ссылок на данные,
хранящиеся в исходном объекте.
• Исключительные ситуации пользователя — создание программистом
собственных исключительных ситуаций для того, чтобы передавать коду,
перехватившему данную исключительную ситуацию, некоторую
дополнительную информацию.
Семейства
В главе 5 вы познакомились с использованием массивов для создания таких
типов переменных, в которых содержится несколько объектов или значений.
У массивов, однако, имеются определенные ограничения. Наиболее существенным
из них является то, что после создания массива его размеры должны оставаться
неизменными, поэтому дописывать новые элементы в конец уже существующего
массива нельзя — требуется создавать новый массив. В результате синтаксис, ис-
пользуемый для работы с массивами, часто оказывается чрезмерно перегруженным.
Методы ООП позволяют создавать классы, которые выполняют существенную
часть этих манипуляций внутренним образом, упрощая тем самым код, в котором
используются списки элементов или массивы.
228 Глава 11
Массивы в С# реализованы как экземпляры класса system.Array и представ-
ляют собой просто один из тех типов, которые известны под названием классов
семейств. Классы семейств, вообще говоря, используются для работы со списками
объектов и по сравнению с обычными массивами обладают некоторыми дополни-
тельными функциональными возможностями. Эти возможности возникают за счет
реализации интерфейсов из пространства имен System.collections, что позволяет
стандартизировать синтаксис семейств. В пространстве имен содержатся также
и некоторые другие интересные вещи, например, классы, которые реализуют ин-
терфейсы ОТЛИЧНЫМИ ОТ System.Array Способами.
Функциональные возможности семейств, в том числе такие основополагающие
функции, как доступ к членам семейств с использованием синтаксиса [индекс],
реализуются посредством интерфейсов, причем мы не ограничены базовыми клас-
сами семейств, таких как system.Array. Напротив, у пользователей имеется воз-
можность создавать свои собственные классы семейств. Такие классы могут
в большей степени соответствовать объектам, с которыми их планируется исполь-
зовать (т. е. тем объектам, из которых планируется создавать семейство). Одно из
преимуществ такого подхода заключается в том, что, как мы увидим в дальнейшем,
создаваемые пользователем классы семейств могут быть строго типизированы.
Это означает, что при извлечении элементов из семейства не требуется приводить
их типы к требуемому типу.
В пространстве имен system.collections существуют интерфейсы, которые
обеспечивают выполнение базовых функциональных возможностей семейств:
• innumerable позволяет просматривать элементы семейств в цикле.
• ICollection (наследуемый ОТ IEnumerable) ПОЗВОЛЯет ПОЛучать
общее число элементов в семействе, а также копировать
элементы семейства в обычный массив.
• IList (который наследуется ОТ IEnumerable И ICollection)
предоставляет список элементов семейства и доступ к этим элементам,
а также некоторые другие базовые возможности, имеющие отношение
к работе со списками элементов.
• IDictionary (наследуемый ОТ IEnumerable и ICollection) аналогичен
интерфейсу iList, но предоставляет список элементов, доступ к которым
осуществляется не с помощью индекса, а посредством некоторого
ключевого значения.
Класс System.Array наследуется ОТ IList, ICollection И IEnumerable, ОДНаКО
он не поддерживает некоторые наиболее мощные возможности интерфейса IList
и представляет список элементов только фиксированной длины.
Использование сенейств
Класс System.Collections.ArrayList — один из классов в пространстве имен
System.Collections — тоже реализует интерфейсы IEnumerable, ICollection И
iList, однако делает это более изощренным способом, чем класс system.Array.
В то время как массивы обладают фиксированным размером (мы не имеем воз-
можности добавлять или исключать элементы), этот класс может использоваться
для представления списков элементов переменной длины. Чтобы получить пред-
ставление о том, какими возможностями обладают такие усложненные семейства,
давайте рассмотрим пример, в котором наряду с таким классом будет использо-
ваться и обыкновенный массив.
Дополнительные сведения о классах 229
Практикум: сравнение массивов и более сложных семейств
1. Создайте новое консольное приложение с именем chiiExOi
В Директории C:\BegCSharp\Chapterll.
2. Добавьте новый класс Animal (животное) к проекту
в файле Animal.cs, воспользовавшись мастером Add Class.
3. Внесите следующие изменения в Animal.cs:
namespace ChllExOl
{
public abstract class Animal
{
protected string name;
public string Name
Щ
return name;
name = value;
••}
public Animal()
name = "The animal with no name";
public Animal(string newName)
name = newName;
public void Feed()
Console.WriteLine("{0} has been fed.", name);
public class Cow : Animal
public void Milk()
Console.WriteLine(*{0} has been milked.*, name),
public Cow(string newName) : base(newName)
public class Chicken : Animal
{
public void LayEggO
{
Console.WriteLine("{0} has laid an egg.*, name);
}
public Chicken(string newName) : base(newName)
230 Глава 11
4. Внесите следующие изменения в classl.cs:
using System;
namespace ChllExOl
/// <summary>
/// Summary description for Classl.
/// </summary>
class Classl
static void Main (string [] args)
Console.WriteLine("Create an Array type collection of Animal " +
Animal E3 animalArray = new Animal [23;
:-.•.••;• Cow myCowl = new Cow('Deirdre') ;
animalArray[0] = myCowl;
animal Array [ 1 ] = new Chicken ( "Ken*) ;
for each (Animal myAnimal in animalArray)
{ . .-•••'
Console.WriteLine("New {0> object added to Array collection, * +
'Name = {1}*, myAnimal.ToString(), myAnimal.Name);
animalArray[0].Feed();
( (Chicken) animalArray [1] ) .LayEggO ;
• • • '• ' ; • : • • • :':
• -V': .- :: : '::•: ' V '
Cow myCow2 = new Cow('Hayley') ;
animalArrayList.Add(myCow2);
animalArrayList.Add(new Chicken("Roy*));
foreach (Animal myAnimal in animalArrayList)
Console.WriteLineCNew {0} object added to ArrayList collection,* +
Ш. 'Name = {1}*/ myAnimal .ToString (), my Animal. Name );
Console.WriteLine('ArrayList collection contains {0} objects.*.
animalArrayList.Count);
( (Animal) animalArrayList [0]) .FeedO ;
( (Chicken) animalArrayList [1]) .LayEggO ;
Console.WriteLine('Additional manipulation of ArrayList:*);
animalArrayList.RemoveAt(0);
( (Animal) animalArrayList [0]) .FeedO ;
animalArrayList.AddRange(animalArray);
((Chicken)animalArrayList[2]).LayEggO;
Console.WriteLine('The animal called {0} is at index {!}.",
myCowl.Name, animalArrayList.IndexOf(myCowl));
Console.WriteLineCThe animal is now called (0).*.
Дополнительные сведения о классах 231
5. Запустите приложение:
I *••• '• C;\8ea£$harp\€h• ill кО ? \ ^ШйШШл2
*e?t$ ami .wse tt*
New €MlEx01.€ou object added.to Йтчи/ collections, ^Каюе' ^. 0eivdre
Hew ClitlEx01..C1t£ck&n objectзиЫес! to ..-ftaw&y-'collection:* Нагяе **• Ken
йр**«*у collection contains 2 objects.
Beii*dre b&s Ъе^я fed»
Hen has laid an »$#*
Create an ftwaybist t^jje collection-of .йп£гоаХ objects and us© its.
New €М1£х8Х*С<ш object added, to fti44^Li$t .collection* Nar>ie '~'Hayley
New CKti£x61.Cbick«:n object ftdclad to ft*»**ayl*i$t collection^' Name да В0
ftrj*ayl»i*t collectiort-contains 2 objects.
Hay ley lias fee«n fell*
Йоу has laid an 003^1
Ken has k i d ЙЙ eg^-.
The diiinal called &£ii4h*&
The aninal is -now called <
Prese--'any key to continue
/(а/с это работает
В этом примере создается два семейства объектов, причем первое — с исполь-
зованием класса System.Array (т. е. обыкновенный массив), а второе — с исполь-
зованием класса system.ArrayList. Оба они являются семействами объектов
класса Animal, который определен в файле Animai.cs. Класс Animal является аб-
страктным классом, следовательно, создание экземпляров этого класса является
недопустимым; однако за счет использования полиморфизма имеется возможность
включить в состав семейства экземпляры класса cow и класса chicken, которые
ЯВЛЯЮТСЯ ПРОИЗВОДНЫМИ ОТ класса Animal.
Эти массивы создаются в методе Main о в файле ciassi.es, после чего над
ними производятся различные манипуляции, показывающие их характеристики
и возможности. Некоторые из продемонстрированных операций применимы как
к семейству Array, так и к семейству ArrayList, хотя и имеются незначительные
отличия в синтаксисе. Однако есть и такие операции, выполнение которых оказы-
вается ВОЗМОЖНЫМ ТОЛЬКО С более СЛОЖНЫМ ТИПОМ ArrayList.
Мы начнем с рассмотрения простых операций, сравнивая код и полученные ре-
зультаты для обоих типов семейств.
Итак, первое — создание семейства. Для того чтобы использовать обычный
массив, необходимо инициализировать его с фиксированными размерами. Мы вы-
полняем эти действия над массивом с именем animaiAtrray, применяя стандартный
синтаксис из главы 5:
Animal [] animalArray = new Animal [2];
С другой стороны, для семейств ArrayList при инициализации задание размера
не требуется, поэтому список (с именем animalArrayList) может быть создан сле-
дующим образом:
ArrayList animalArrayList = new ArrayList();
Существует два других конструктора, которые могут быть использованы для
данного класса. Первый осуществляет копирование содержимого уже существую-
щего семейства в новый экземпляр, при этом семейство передается ему в качестве
параметра; второй конструктор позволяет задать емкость семейства и тоже по-
средством параметра. Эта емкость, задаваемая значением типа int, устанавливает
начальное количество элементов, которые могут содержаться в данном семействе.
232 Глава 11
На самом деле это не является реальной емкостью семейства, поскольку емкость
автоматически удваивается, если число элементов превышает заданное значение.
В случае массивов, состоящих из элементов ссылочных типов (как используемые
объекты класса Animal и производных от него классов), простая инициализация
массива с указанием размера не влечет за собой инициализации его элементов.
Для того чтобы воспользоваться некоторым вхождением, его также необходимо
инициализировать, откуда следует, что элементам массива должны быть присвоены
инициализированные объекты:
Cow myCowl = new CowCDeirdre") ;
animalArray[0] = myCowl;
animal Array [ 1 ] = new Chicken ("Ken*) ;
В этом коде процедура выполняется двумя способами: один раз — путем при-
сваивания уже существующего объекта класса cow и второй раз — через создание
нового объекта класса chicken. Основное отличие между этими способами заклю-
чается в том, что в первом случае мы получаем ссылку на объект, находящийся
в массиве; этот факт мы используем в нашей программе позднее.
В семействе ArrayList не существует никаких элементов, даже ссылающихся
на null. Это означает, что мы лишены возможности присваивать новые объекты
аналогичным образом, с использованием индексов. Вместо этого для включения
НОВЫХ объектов Необходимо ВОСПОЛЬЗОВаТЬСЯ МеТОДОМ Add() Объекта ArrayList:
Cow myCow2 = new Cow (* Hay ley") ;
animalArrayList.Add(myCow2);
animalArrayList. Add (new Chicken ( "Roy ") ) ;
Несколько изменив синтаксис, можно точно таким же образом добавлять в се-
мейство новые или уже существующие объекты.
После того как элементы добавлены, появляется возможность перезаписать их,
используя синтаксис, идентичный применяемому для работы с массивами. Например:
animalArrayList [0] = new Cow(*Alma") ;
Правда, в настоящем примере мы таких действий предпринимать не будем.
В главе 5 вы познакомились с тем, как использовать конструкцию foreach
для прохода по элементам массива. Это оказывается возможным, поскольку класс
System.Array реализует интерфейс iEnumerabie, а единственный метод этого ин-
терфейса, GetEnumerator(), позволяет проходить в цикле по всем элементам се-
мейства. Более подробно мы будем рассматривать этот вопрос позже. В нашей
программе мы выводим информацию обо всех объектах класса Animal, содержа-
щихся в массиве:
foreach (Animal myAnimal in animalArray)
{
Console.WriteLineCNew {0} object added to Array collection, * +
"Name = {1}", myAnimal.ToString(), myAnimal.Name);
}
Объект ArrayList также поддерживает интерфейс iEnumerabie и, следовательно,
может использоваться вместе с конструкцией foreach:
foreach (Animal myAnimal in animalArrayList)
{
Console.WriteLine(*New {0} object added to ArrayList collection," +
• Name = (I)*, myAnimal.ToString(), myAnimal.Name);
Дополнительные сведения о классах 233
Далее для вывода на экран числа элементов, содержащихся в массиве, мы ис-
пользуем свойство массива Length:
Console.WriteLine("Array collection contains {0} objects.*,
animalArray.Length);
Мы можем достигнуть того же самого результата и дая семейства ArrayList,
с тем лишь отличием, что придется воспользоваться свойством count, которое яв-
ляется составной частью интерфейса icoiiection:
Console.WriteLine("ArrayList collection contains {0} objects.",
animalArrayList.Count);
Семейства — независимо от того, являются ли они массивами или более слож-
ными семействами — оказываются не столь полезными, если в них не предусмот-
рено обеспечение доступа к принадлежащим им элементам. Простые массивы
строго типизированы, т. е. они обеспечивают непосредственный доступ к типу сво-
их элементов. Это означает, что можно напрямую вызывать метод их элемента:
animalArray[0].Feed();
Поскольку массив имеет абстрактный тип Animal, то нельзя непосредственно
вызывать методы, предоставляемые производными классами. Вместо этого следует
воспользоваться приведением типа:
((Chicken) animalArray [1]) .LayEggO ;
Семейство ArrayList — это семейство объектов класса system.object (мы при-
своили ему объекты Animal, воспользовавшись полиморфизмом). Это значит, что
необходимо выполнить приведение типа для всех его элементов:
((Animal)animalArrayList[0]).Feed();
((Chicken)animalArrayList[1]).LayEgg();
В оставшейся части кода используются некоторые особенности семейства Animal,
КОТОрые ОТСУТСТВУЮТ у СемеЙСТВа Array.
Во-перВЫХ, МОЖНО удаЛЯТЬ Элементы С ПОМОЩЬЮ МеТОДОВ Remove О И RemoveAtO,
которые являются составной частью реализации класса ArrayList. Эти методы
производят удаление элемента из массива по ссылке и по индексу соответственно.
В нашем примере для удаления первого включенного в список элемента — объек-
та класса cow, свойство Name которого имеет значение "Hayley" — используется
второй метод:
animalArrayList.RemoveAt(0) ;
В качестве альтернативы можно было бы применить следующий код:
animalArrayList.Remove(myCow2);
Поскольку на этот объект уже имеется локальная ссылка, то существующую ссылку
мы добавили в массив вместо того, чтобы создавать новый объект.
Как бы то ни было, единственным элементом, оставшимся в семействе, является
объект класса chicken, доступ к которому мы осуществляем следующим образом:
((Animal)animalArrayList[0]).Feed();
Выполнение любых операций над элементами объекта ArrayList, в результате
которых в этом массиве останется N элементов, будет производиться таким обра-
зом, что этим элементам будут соответствовать индексы в диапазоне от 0 до N-I. .
234 Глава 11
Так, например, удаление элемента с номером 0 приводит к тому, что все остальные
элементы сдвигаются в массиве на одну позицию, поэтому доступ к объекту класса
chicken осуществляется с индексом 0, а не 1. А поскольку теперь в массиве не су-
ществует элемента с индексом 1 (с самого начала было всего два элемента), то при
попытке выполнить следующий код будет сгенерирована исключительная ситуация:
((Animal)animalArrayList[1]).Feed();
Семейства ArrayList позволяют добавлять сразу по несколько элементов с по-
мощью метода AddRange (). Этому методу может передаваться произвольный объект,
обладающий интерфейсом ICollection, В КОТОрыЙ ВХОДИТ маССИВ animalArray:
animalArrayList.AddRange(animalArray);
Теперь для подтверждения того, что это работает, мы можем попытаться осу-
ществить доступ к третьему элементу семейства, который будет являться вторым
элементом В animalArray:
((Chicken)animalArrayList[2]).LayEgg();
Метод AddRange о не является частью ни одного из интерфейсов, предоставляе-
мых классом ArrayList. Этот метод присущ именно классу ArrayList и демонстри-
рует тот факт, что в классах семейств мы можем описывать определяемое
пользователем поведение, которое далеко выходит за рамки рассмотренных выше
интерфейсов. Этот класс предоставляет также множество других интересных ин-
терфейсов, например, insertRangeO, который позволяет вставлять массив объек-
тов в любую точку списка, а также методы для выполнения таких задач, как
сортировка и изменение порядка следования элементов.
Наконец, мы вновь задействуем возможность использовать несколько ссылок на
один и тот же объект. Применив метод indexof (), являющийся частью интерфейса
iList, мы можем узнать не только то, что теперь объект myCowl (который мы изна-
чально Добавили К массиву animalArray) ВХОДИТ В СемеЙСТВО animalArrayList, НО
также и каков его индекс:
Console.WriteLine(*The animal called {0} is at index {1}.*,
myCowl.Name, animalArrayList.IndexOf(myCowl));
Следующие две строки кода переименовывают объект с помощью ссылки на
него и выводят новое имя на экран, используя для этого ссылку семейства:
myCowl.Name = "Janice";
Console.WriteLineCThe animal is now called {0}.",
( (Animal) animalArrayList [1]) .Name) ;
Определение семейств
Теперь, когда мы познакомились с теми возможностями, которые предоставля-
ются при использовании более сложных классов семейств, настала пора узнать,
каким образом можно создавать собственные строго типизированные семейства.
Один из способов — это реализовать все необходимые методы вручную, однако
такой подход может потребовать очень больших затрат времени, а в некоторых
случаях оказаться чрезвычайно сложным. В качестве альтернативы существует
возможность создать семейство, являющееся производным от некоторого класса,
например, от system.collections.coiiectionBase — абстрактного класса, в кото-
ром реализована большая часть семейств. Мы рекомендуем использовать именно
такой способ.
Дополнительные сведения о классах 235
Класс CollectionBase предоставляет интерфейсы IEnumerable, ICollection
и iList, однако в них реализованы только некоторые из необходимых методов,
а именно методы clear о и RemoveAto интерфейса iList, а также свойство count
интерфейса ICollection. Если возникает необходимость в каких-либо дополни-
тельных функциональных возможностях, то их приходится реализовывать само-
стоятельно.
Для этого в классе CollectionBase предусмотрено два защищенных свойства,
которые позволяют осуществлять доступ собственно к хранящимся объектам.
Можно воспользоваться свойством List — оно обеспечивает доступ к элементам
посредством интерфейса iList, и свойством innerList — оно является объектом
ArrayList и используется доя хранения элементов.
Например, класс семейства для хранения объектов Animal может быть в общих
чертах определен следующим образом (вскоре мы познакомимся с более полным
определением):
public class Animals : CollectionBase
{• •• / • •" : • ;' "'• • ••'••••••. ;• • • :''.'•• .••".• ' ' /• / ; ..л-:;-"'" "• '• ':
public void Add(Animal newAnimal)
List.Add(newAnimal);
public void Remove(Animal oldAnimal)
{
List.Remove(oldAnimal);
} ... , . Щ
•• public AnimalsO
.:' ШМ ' У- • : Ш Ш ':.:•• ( ' ' .. . . .V . . ! .
. * ' - : • • • . - . )
В данном случае Add о и Remove о реализованы как строго типизированные ме-
тоды, которые используют стандартный метод Add о интерфейса iList для осуще-
ствления доступа "к элементам. Представленные методы будут работать только
с классами Animal или с классами, производными от Animal, в отличие от встре-
чавшихся нам ранее реализаций ArrayList, которые допускали использование про-
извольных объектов.
Класс CollectionBase ПОЗВОЛЯеТ ИСПОЛЬЗОВатЬ СИНТаКСИС foreach ДЛЯ ПрОИЗВОД-
ных семейств. Можно, например, написать следующий код:
Console.WriteLine("Using custom collection class Animals:");
Animals animalCollection = new AnimalsO;
animalCollection. Add (new Cow ("Sarah"));
foreach (Animal myAnimal in animalCollection)
{
;• : Console.WriteLine("New {0} object added to custom collection, * +
"Name = {1}*, myAnimal.ToString()> myAnimal.Name);
} , '
А вот этот код является недопустимым:
animalCollection[0].Feed();
Для того чтобы осуществлять доступ к элементам по их индексам таким спосо-
бом, нам придется воспользоваться индексатором (indexer).
236 __ Глава 11
Индексаторы
Индексатор — это особый вид свойства, которое можно добавлять в класс для
обеспечения способа доступа, аналогичного доступу к массивам. В действительно-
сти индексаторы позволяют реализовать еще более сложные способы доступа,
поскольку при необходимости разрешают определять и применять сложные типы
параметров с использованием квадратных скобок; несмотря на это, применение
простого численного индекса для доступа к элементам является наиболее распро-
страненным.
Мы можем добавить индексатор в семейство Animals объектов Animal следую-
щим образом:
public class Animals : CollectionBase
{
public Animal this[int animallndex]
{
get
{
return (Animal)List[animallndex];
} "
Ш. " set • -' • •'•^ ••••
List[animallndex] = value;
> |§l!iitlli ••"•.:': : ' ! , ' • ЩШШ
}
Ключевое слово this используется с параметрами, задаваемыми в квадратных
скобках, однако во всем остальном оно выглядит почти точно так же, как и любое
другое свойство. Такой синтаксис представляется весьма логичным, поскольку мы
собираемся осуществлять доступ к индексатору, используя имя объекта, за кото-
рым следует один или несколько параметров, заключенных в квадратные скобки
(например, MyAnimals[O]).
Этот код использует индексатор для свойства List (т. е. для интерфейса iList,
Обеспечивающего ДОСТуП К СемеЙСТВу ArrayList класса CollectionBase, В КОТОрОМ
хранятся наши элементы):
return (Animal) List [animallndex] ;
Явное приведение типа является в данном случае совершенно необходимым,
ПОСКОЛЬКУ СВОЙСТВО IList.List ВОЗВращаеТ Объект класса System.Object.
Следует обратить внимание на то, что нам приходится определять тип индекса-
тора. Это тот тип, который будет получаться при обращении к элементу посредст-
вом данного индексатора. Это означает, что можно использовать следующий код:
animalCollection[0].Feed();
вместо:
((Animal)animalCollection[0]).Feed О;
Это еще одна удобная возможность, которую предоставляют строго типизиро-
ванные семейства, создаваемые пользователем. Давайте расширим наш последний
пример, чтобы воспользоваться этим подходом на практике.
Дополнительна nOo^nn
237
-
VU New Folder
Add Windows Form,..
Щ Add Inherited Form.
Щ Add User Control,..
Add Inherited Control,,
$3 Add Component..,
f | Add Class...
3. Выберите файл Animal.cs
в директории
С:\BegCSharp\Chapterll\ChllEx01
и нажмите Open (см. рис. слева).
4. Измените объявление
Пространства имен В Animal.cs
следующим образом:
namespace ChllExO2
6. Измените код в Ani.ai«.c, Ыедую щ„„ о б р а з о м :
ИuЯsТiТn1ГgТ CSSyres4+te-»m-4_,;.
using System.Collections;
namespace ChllExO2
r
Public class Animals : CollectionBase
public void Add(Animal newAnimal)
List,Add(newAnimal);
public void Remove(Animal newAnimal)
List.Remove(newAnimal);
public Animals()
public Animal thia[int animallndex]
get
^ return (Animal)List[animallndex];
set
List[animallndex] = value;
238 Глава 11
7. Модифицируйте код в ciassi.cs следующим образом:
static void Main(string[] args)
Animals animalCollection = new Animals();
animalCollection.Add(new Cow("Jack"));
animalCollection.Add(new Chicken("Vera"));
foreach (Animal myAnimal in animalCollection)
myAnimal.Feed();
8. Запустите приложение:
m C:\BegCSharp\Chap
Jack has beet* fed*
Uera "lias, been Fed*
Press ans? key to continue
welcome to the C# А<Ш imioxer Wizard
Как это работает
В этом примере используется подробно разобранный в предыдущем разделе
код, предназначенный для реализации строго типизированного семейства объектов
Animal в классе с именем Animals. Код в Main о создает экземпляр объекта класса
Animals с именем animalCollection, добавляет в него два элемента (по одному
элементу класса cow и класса chicken) и использует цикл foreach для вызова ме-
тода Feed о , который оба этих объекта наследуют от своего общего базового клас-
са Animal.
Мастер Add Indexer (добавление индексатора)
В VS существует еще один мастер, используемый в рамках ООП на С # ,— Add
Indexer. Он располагается в том же месте, где и мастера для добавления свойств,
методов и полей (контекстное меню в окне Class View, пункт Add | Add Indexer...).
Мастер открывает диалоговое окно, показанное на рисунке слева.
Мастер Add Indexer позволяет задавать
уровень защищенности (в данном случае
public, поскольку необходимо, чтобы ин-
дексатор был доступен из внешнего по от-
ношению к данному семейству кода) и тип
индексатора (в данном случае Animal, по-
скольку требуется, чтобы индексатор был
строго типизирован и возвращал элемен-
ты класса Animal). Можно также задавать
типы и имена параметров; в настоящем
примере мы использовали один параметр
типа int с именем animaiindex. Наконец,
появилась возможность задать какие-
либо описатели для данного индексатора:
мы можем определить данный индексатор
Дополнительные сведения о классах 239
либо как виртуальный, либо (в случае абстрактного класса) как абстрактный. Если
индексатор объявлен как virtual, это означает, что переопределение данного ин-
дексатора допускается во всех классах, производных от индексируемого класса,—
точно так же, как и переопределение виртуальных методов, с которыми вы позна-
комились в предыдущей главе. Абстрактные индексаторы ничем не отличаются от
виртуальных индексаторов, однако для их реализации не задается никакого кода —
это обязанность возлагается на классы, являющиеся производными от индексируе-
мого класса.
Изображенные выше установки позволяют получить следующую структуру для
индексатора класса Animals:
public Animal this(int animallndex)
{
get
{
return null;
}
set
Заметьте, что наполнение блоков get и set необходимо задавать вручную.
Семейства с доступом по ключу
и интерфейс IDictionary
Кроме интерфейса iList, в семействах можно реализовать аналогичный интер-
фейс IDictionary, который позволяет осуществлять доступ к элементам по значе-
нию ключа (например, по строке с именем) вместо доступа по индексу.
Это тоже достигается с помощью индексатора, однако в данном случае параметр
индексатора привязывается к хранящемуся элементу, а не к индексу типа int,
что позволяет сделать семейство несколько более дружественным по отношению
к пользователю.
Точно так же, как и для индексируемых семейств, в данном случае имеется ба-
зовый класс, который можно использовать для упрощения реализации интерфейса
IDictionary,— класс DictionaryBase. В этом классе также реализованы интер-
фейсы iEnumerabie и icoiiection, что обеспечивает единые для всех семейств
основные возможности манипуляций.
Класс DictionaryBase, как и класс coiiectionBase, реализует некоторые (но не
все) члены, полученные посредством поддерживаемых им интерфейсов. Так же,
как и в классе CoiiectionBase, в нем реализованы методы clear о и count о , а вот
Метод RemoveAtO — нет. Эта ситуация объясняется тем, ЧТО RemoveAtO — ЭТО
метод интерфейса iList, и он реализуется для интерфейса IDictionary. Однако
IDictionary обладает методом RemoveO, являющимся одним из тех методов, кото-
рые необходимо реализовать для создаваемых пользователем классов семейств,
основанных на классе DictionaryBase.
Следующий код являет собой пример альтернативной версии класса Animals из
предыдущего раздела, который на этот раз в качестве базового класса использует
класс DictionaryBase. В него входят методы Add(), RemoveO и индексатор доступа
по ключу:
public class Animals : DictionaryBase
{
public void Add(string newID, Animal newAnimal)
240 Глава 11
Dictionary.Add(newID, newAnimal);
public void Remove(string animallD)
Dictionary.Remove(animalTD);
public Animals()
public Animal this[string animallD]
' ; ••-.' 9e t ' V': '', ^, ' : : ; ' : ^ ' - : : : 7 " . . 7 '•••• • : ' • -• ' • : ' ••'•: •
: return (Animal} Dictionary [animallD] ;;
• -'• • ' • • ' " • • Set . : . ' . • • . ' ' : : ' ; " ; • : ; V ; ; : : ' : ; / - ' , . : ; . ' . ' : ••••••••• ' ,• . ! \ ' • - Ч ' ; •' :• • • ' • • : . . • . • . • • : • - . '• -
Dictionary[animallD] = value; •
Вот какие различия имеются между этими членами:
• Add о принимает два параметра: ключ и значение, которые
будут храниться совместно. У данного семейства, построенного
по алфавитному принципу, есть член с именем Dictionary, наследуемый
ОТ класса DictionaryBase. О н ЯВЛЯетСЯ Интерфейсом IDictionary,
имеющим собственный метод Add о с двумя параметрами типа object.
Наша реализация предусматривает передачу значения типа string
в качестве ключа и передачу объекта типа Animal.
• Remove о получает в качестве параметра ключ, а не ссылку на объект.
Элемент, обладающий значением, заданным с помощью ключа, удаляется.
• Индексатор использует вместо индекса значение ключа в виде строки,
что позволяет осуществлять доступ к хранящемуся элементу
через наследуемый член Dictionary. Повторим еще раз,
что в данном случае приведение типа является обязательным.
Еще одним отличием семейств с базовым классом DictionaryBase от семейств
с базовым классом coiiectionBase является то, что для них немного по-разному
выполняется конструкция foreach. Семейство из предыдущего раздела допускает
непосредственное извлечение объектов типа Animal. Использование конструкции
foreach С КЛЗСС0М, ПРОИЗВОДНЫМ ОТ DictionaryBase, ПрИВОДИТ К получению СТрук-
тур типа DictionaryEntry — еще одного типа, описанного в пространстве имен
System.Collections. Для того чтобы добраться до самих объектов типа Animal, не-
обходимо воспользоваться членом этой структуры value, хотя для получения доступа
к соответствующему ключу можно также использовать член данной структуры кеу.
Теперь использовавшемуся ранее коду:
foreach (Animal myAnimal in animalCollection)
{
Console.WriteLineCNew {0} object added to custom collection, * +
"Name = {1}*, myAnimal.ToString(), myAnimal.NameO );
Дополнительные сведения о классах ' 241
будет эквивалентен следующий код:
foreach (DietionaryEntry myEntry in -animalCollection)
{
Console.WriteLine("New {0} object added to custom collection, " +
"Name = {1}*, myEntry.Value.ToString(),
((Animal)myEntry.Value).Name);
> - ' rib ;:•;
На самом деле можно переопределить это поведение таким образом, чтобы извле-
кать объекты непосредственно с помощью конструкции foreach, однако данная
тема чрезвычайно сложна, и мы ее рассматривать не будем.
Посвященная этому вопросу статья Карли Ватсона
Опубликована На Web-Сайте CSharpToday.com.
Усовершенствование CardLib, часть 1
В предыдущей главе был создан проект библиотеки классов под названием
chiocardLib, в котором содержался класс Card, представляющий игральные карты,
и класс Deck, нредставляющий колоду карт, т. е. являющийся семейством объектов
класса card. Это семейство было реализовано в виде простого массива.
Теперь добавим в эту библиотеку, которую переименуем в chiicardLib, еще
один класс. Этот новый класс — назовем его Cards — будет представлять создан-
ное пользователем семейство объектов типа Card, что позволит использовать пре-
имущества, о которых рассказывалось в данной главе. Код, описывающий этот
класс, находится в файле cards.es и имеет следующий вид (изменения в коде, со-
зданном мастером, выделены серым цветом):
using Systern;
using System.Collections;
namespace ChllCardLib
{
/// <summary>
/// Summary description for Cards.
///</summary>
public class Cards : CollectionBase
{
public void Add (Card newCard)
{
List.Add(newCard);
public void Remove (Card oldCard)
{ ^--- • • : • • • : • • : • • • v - ; : / ; . - : • • • . • • • • • • • '•>
List.Remove(oldCard) ;
public Cards()
public Card this[int cardlndex]
{ '
get
return (Card)List[cardlndex];
}
set
{
List[cardlndex] = value;
242 Глава 11
II Проверка, позволяющая определять, содержится ли в семействе
/7хCards конкретная карта. С этой целью осуществляется вызов метода
Теперь нам предстоит внести определенные изменения в файл Deck.cs, чтобы
он мог использовать это новое семейство вместо массива:
using System;
namespace ChllCardLib
{
public class Deck
{
private Cards cards = new Cards ();
public Deck()
,.........{
// здесь располагалась удаленная строка кода.
for (int suitVal = 0; suitVal < 4; suitVal++)
{
for (int rankVal = 1; rankVal < 14; rankVal++)
{
cards. Add (new Card ( (Suit) suitVal, (Rank) rankVal) );
public Card GetCard(int cardNum)
{
if (cardNum >= 0 && cardNum <= 51)
return cards[cardNum] ;
else
throw (new System. ArgumentOutOf RangeException ("cardNum*, cardNum,
"Значение должно находиться в диапазоне между 0 и 51."));
}
public void Shuffle()
{
' Cards newDeck = new Cards();
bool[] assigned = new bool[52];
e for (int i = 0; i < 52; i++)
ШШШ • ' : \ • .. £
bool foundCard = false;
while (foundCard == false) P sourceCard = sourceGen.Next(52);
ШМШ if (assigned[sourceCard] == false) . ;.:
foundCard = true;
}
assigned[sourceCard] = true;
>: newDeck.Add(cards[sourceCard]);
}
cards = newDeck;
Дополнительные сведения о классах 243
Обратите внимание, что при копировании исходных файлов
U3 ChlOCardLib в ChllCardLib необходимо изменить объявления
пространства имен таким образом, чтобы они ссылались
на chiicardbib; именно поэтому строка с объявлением
пространства имен в коде также выделена. Это справедливо
как для файла card.cs, так и для консольного приложения
ChlOCardClient.
В данном случае потребовалось внести не так уж много изменений. Большая их
часть касается изменения логики процедуры тасования колоды. Теперь карты вы-
бираются по случайному индексу и добавляются в начало нового семейства эле-
ментов cards с именем newDeck, а раньше они выбирались по последовательным
индексам из cards и добавлялись в newDeck по индексу, определяемому случайным
образом.
Клиентское КОНСОЛЬНОе приложение ChlOCardClient ДЛЯ решения ChlOCardLib
может работать с новой библиотекой; при этом результаты окажутся точно такими
же, поскольку сигнатуры методов в Deck остались неизменными. Клиенты этой
библиотеки классов теперь могут использовать класс семейства cards, вместо того
чтобы работать с массивами объектов типа card, например, при определении роз-
данных игрокам карт в приложении для карточных игр.
Перегрузка операторов
Следующим предметом рассмотрения будет перегрузка операторов. Она по-
зволяет применять стандартные операторы — такие как + , > и т. п .— в операци-
ях над созданными нами классами. Это называется перегрузкой, поскольку мы
используем нашу собственную реализацию работы этих операторов в тех случаях,
когда они применяются для выполнения операций над параметрами определенных
типов; во многом это аналогично тому, как перегружаются методы, когда переда-
ются различные параметры методам с одним и тем же именем.
Перегрузка операторов оказывается весьма полезной, поскольку позволяет вы-
полнять любые действия, которые мы определим при реализации перегрузки опе-
ратора. Они могут быть вовсе не так просты, как, скажем, "+ означает сложение
двух данных операндов". Немного позже, при дальнейшем усовершенствовании
библиотеки CardLib, нам встретится хороший пример такого подхода. Мы включим
в нее такие реализации операторов сравнения, которые позволят сравнивать две
карты, чтобы определить, какая из них бьет другую и берет взятку (этап карточной
игры). Поскольку взятка во многих карточных играх зависит от мастей участвую-
щих в ней карт, то эта задача оказывается не такой примитивной, чтобы просто
сравнивать старшинство карт. Если вторая сыгранная карта отличается по масти
от первой, то первая карта забирает взятку независимо от старшинства. Такая ло-
гика может быть реализована, если учитывать порядок следования двух операндов.
Мы можем также ввести понятие козырной масти: козыри бьют все остальные ма-
сти даже в том случае, если это не первая сыгранная карта. Это означает, что если
результат cardi > card2 равен true (т. е. cardi бьет card2, если cardi сыграна
первой), из него совершенно не следует, что значение card2 > cardi есть false.
Если ни cardi, ни card2 не являются козырями и масти их различны, то оба эти
сравнения дадут значение true.
Однако для начала давайте познакомимся с основным синтаксисом перегрузки
операторов.
244 Глава 11
Операторы могут перегружаться с помощью включения в класс членов типа
оператора (которые должны быть описаны как static). Для некоторых операторов
возможны различные варианты использования (например, для оператора — он
может использоваться и как унарный, и как бинарный), поэтому необходимо ука-
зывать, с каким количеством операндов мы имеем дело и какого они типа. Обычно
работа ведется с операндами того же типа, что и класс, в котором определяется
данный оператор, хотя возможна работа со смешанными типами, с чем мы позна-
комимся позже.
В качестве примера давайте рассмотрим простой класс с именем Addciassi,
определенный следующим образом:
Категория: информатика | Просмотров: 842 | Добавил: basic | Рейтинг: 5.0/1
Всего комментариев: 0
Имя *:
Email *:
Код *:
Календарь
«  Февраль 2010  »
ПнВтСрЧтПтСбВс
1234567
891011121314
15161718192021
22232425262728
Статистика

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

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