Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Универсальные коллекции языка С#. Учебное пособие

.pdf
Скачиваний:
0
Добавлен:
07.09.2026
Размер:
1 Мб
Скачать
☆
T2, …, TN) задается в заголовке объявления интерфейса между сим-
волами <>.
Общая форма объявления обобщенного интерфейса следующая:
interfaceInterfaceName<T1,T2,...,TN> { //... }
здесь:
 InterfaceName – имя интерфейса;
 T1, T2, …, TN – имена типов, которые выступают в качестве па-
раметров интерфейса.
Класс, реализующий интерфейс InterfaceName<T1, T2, …, TN>, должен объявляться следующим образом:
classClassName<T1,T2,...,TN>:InterfaceName<T1,T2,...,TN> { //... }
здесь ClassName – имя класса, реализующего интерфейс Interface­Name.
В простейшем случае интерфейс получает некоторый тип T. В этом случае общая форма объявления интерфейса и его реализации в классе имеет вид
interfaceInterfaceName<T>
{
//...
}
classClassName<T>:InterfaceName<T>
{
//...
}
В следующей таблице описаны важные для коллекций интерфейсы, которые определяют их свойства и особенности поведения.
11
Интерфейсы, используемые в коллекциях C#
р
r
(
())
р
Интерфейс Описание
IEnumerable<T>
ICollection<T>
IList<T>
ISet<T>
IDictionary<TKey, TValue>
Интерфейс IEnumerable необходим, когда с коллекцией используется оператор foreach. Этот интерфейс определяет метод GetEnumerator(), возвращающий перечислитель, кото-
ый реализует IEnumerato ICollection<T> – этот интерфейс реа­лизован классами обобщенных кол­лекций. С его помощью можно полу­чить количество элементов в кол­лекции (свойство Count) и ско­пировать коллекцию в массив (метод СоруТо()). Можно также добавлять и удалять элементы из коллекции
Add(), Remove(), Clear
Интерфейс IList<T> предназначен для создания списков, элементы которых доступны по своим пози­циям. Этот интерфейс определяет индексатор, а также способы встав­ки и удаления элементов в опреде­ленные позиции (методы Insert() и Remove()). IList<T> унаследован от ICollection<T> Интерфейс ISet<T> появился в вер­сии .NET 4. Этот интерфейс реализу­ется множествами. Он позволяет комбинировать различные множе­ства в объединения, а также прове­рять, не пересекаются ли два мно­жества. ISet<T> унаследован от
ICollection<T>
Интерфейс IDictionary<TKey, TValue> реализуется обобщенными классами коллекций, элементы кото
ых состоят
12
Интерфейс Описание
p
()
р
ILookup<TKey, TValue>
IComparer<T>
IEqualityComparer<T>
IProducerConsumerCollection<T>
Окончание таблицы
из ключа и значения. С помощью этого интерфейса можно получать доступ ко всем ключам и значениям, извлекать элементы по индексатору типа ключа, а также добавлять и уда­лять элементы Подобно IDictionary<TKey, TValue> поддерживает ключи и значения. Однако в этом случае коллекция мо­жет содержать множественные зна­чения для одного ключа Интерфейс IComparer<T> реализует компаратор и используется для сор­тировки элементов внутри коллекции с помощью метода Com
are
Интерфейс IEqualityComparer<T> реализует компаратор, который мо­жет быть применен к ключам слова­ря. Через этот интерфейс объекты могут быть проверены на эквива­лентность друг другу. В .NET 4 этот интерфейс также реализован масси­вами и ко
тежами Интерфейс IProducerConsumerCollec­tion<T> был добавлен в версию .NET 4 для поддержки новых, безопасных в отношении потоков классов кол­лекций
КОНТРОЛЬНЫЕ ВОПРОСЫ
1. Что такое интерфейсы (interface) и для чего они используются?
2. Что такое униврсальные интерфейсы? Особенности их описания.
3. Что можно определить в интерфейсы?
4. Можно ли создавать экземпляры интерфейса?
13
ИТЕРАТОРЫ
С помощью итераторов можно получить последовательный доступ
ко всем элементам коллекции, таким как список или массив.
Итератор – включает оператор цикла foreach и метод итератора, ко­торый должен возвращать значение одного из следующих типов: IEnumerable, IEnumerable<T>, IEnumerator или IEnumerator<T>. Мето­дом итератора может быть метод доступа get.
Метод итератора или метод доступа get выполняет настраиваемую итерацию по коллекции. return для поочередного возврата каждого элемента. При достижении инструкции yield return текущее расположение в коде запоминается. При следующем вызове функции итератора выполнение возобновляет­ся с этого места.
Метод итератора вызывается из клиентского кода с помощью опе­ратора foreach или с помощью запроса LINQ.
В приведенном ниже примере первая итерация цикла foreach при­водит к вызову метода итератора SomeNumbers, пока не будет достиг­нут первый оператор yield return. Эта итерация возвращает значение 3. Текущее расположение в методе итератора сохраняется. В следующей итерации цикла выполнение метода итератора возобновляется с того же места и снова приостанавливается при достижении оператора yield return. Эта итерация возвращает значение 5. Текущее расположение в методе итератора снова как будет возвращено значение 8 и достигнут конец метода итератора. Для завершения итерации можно использовать оператор yield break.
Метод итератора использует оператор yield
сохраняется. Цикл завершается после того
staticvoidMain() {
foreach(intnumberinSomeNumbers()) { Console.Write(number.ToString()+""); } //Output:358 Console.ReadKey(); }  publicstaticSystem.Collections.IEnumerableSomeNumbers()
14
{ yieldreturn3; yieldreturn5; yieldreturn8;}
Все примеры в этом разделе, кроме примера простого итератора, включают директивы using для пространств имен System.Collections и System.Collections.Generic.
В следующем примере простого итератора метод итератора Even‐ Sequenceимеет один оператор yield return, который находится внутри цикла for. В методе Main каждая итерация оператора foreach создает вызов метода итератора EvenSequence, который выполняет следующий оператор yield return. Таким образом, цикл foreach через метод
итера­тора EvenSequence получит все целые четные значения, которые при­нимает переменная number.
staticvoidMain() {
foreach(intnumberinEvenSequence(5,18)) { Console.Write(number.ToString()+""); } //Рузультат:681012141618 Console.ReadKey(); }  publicstaticSystem.Collections.Generic.IEnumerable<int> EvenSequence(intfirstNumber,intlastNumber) { //Выдаетчетныечислаиздиапазона. for(intnumber=firstNumber;number<=lastNumber;number++) { if(number%2==0) { yieldreturnnumber; } } }
15
СОЗДАНИЕ ИТЕРАТОРА
ДЛЯ ПОЛЬЗОВАТЕЛЬСКОГО КЛАССА КОЛЛЕКЦИИ
В следующем примере класс DaysOfTheWeek (дни недели) реали­зует интерфейс IEnumerable, которому требуется метод GetEnumerator. Этот метод и является методом итератора, обеспечивающим доступ ко всем элементам массива days.
Компилятор неявно вызывает метод GetEnumerator при выполне­нии оператора цикла foreach, который возвращает значение типа
IEnumerator, с его помощью цикл foreachполучает
доступ к очеред-
ному элементу массива days.
Метод GetEnumerator возвращает каждую строку поочередно с по­мощью оператора yield return.
staticvoidMain() { DaysOfTheWeekdays=newDaysOfTheWeek();  foreach(stringdayindays) { Console.Write(day+""); } //Результат:ВсПнВтСрЧтПтСб Console.ReadKey(); }  publicclassDaysOfTheWeek:IEnumerable { privatestring[]days={"Вс","Пн","Вт","Ср",  publicIEnumeratorGetEnumerator() { for(intindex=0;index<days.Length;index++) { //Возвращаеткаждыйденьнедели. yieldreturndays[index]; } }  } Результатработы: ВсПнВтСрЧтПтСб
"Чт","Пт","Сб"};
16
ИСПОЛЬЗОВАНИЕ ИТЕРАТОРОВ С УНИВЕРСАЛЬНЫМ СПИСКОМ
Для работы с универсальными коллекциями требуются универ­сальные итераторы.
В следующем примере универсальный класс Stack<T> реализует универсальный интерфейс IEnumerable<T>. Метод Push заносит в эле­мент массива, находящийся на вершине стека, значение типа T. Уни­версальный метод GetEnumerator возвращает очередной элемент мас­сива values[index]с помощью оператора yield return.
Помимо универсального метода GetEnumerator должен
быть реали­зован и неуниверсальный метод GetEnumerator. Это связано с тем, что IEnumerable<T> наследуется от IEnumerable. Неуниверсальная реали­зация подчиняется универсальной реализации.
В этом примере используются именованные итераторы для под­держки различных способов итерации по одной и той же коллекции данных. Эти именованные итераторы являются свойствами TopToBottom и BottomToTop и методом TopN.
Свойство BottomToTop использует итератор в
staticvoidMain() { Stack<int>theStack=newStack<int>();  //Добавитьэлементвстек. for(intnumber=0;number<=9;number++) { theStack.Push(number); }  //Выбратьэлементизстека. //foreachприменим,потомучтоtheStackреализует //IEnumerable<int>. foreach(intnumberintheStack) { Console.Write("{0}",number); } Console.WriteLine(); //Output:9876543210  //foreachприменим,потомучто
методе доступа get.
17
//theStack.TopToBottomвозвращаетIEnumerable(целого). foreach(intnumberintheStack.TopToBottom) { Console.Write("{0}",number); } Console.WriteLine(); //Результат:9876543210  foreach(intnumberintheStack.BottomToTop) { Console.Write("{0}",number); } Console.WriteLine(); //Результат:01234  foreach(intnumberintheStack.TopN(6)) { Console.Write("{0}",number); } Console.WriteLine(); //Результат:9876543  Console.ReadKey(); }  publicclassStack<T>:IEnumerable<T> { privateT[]values=newT[100]; privateinttop=  publicvoidPush(Tt) { values[top]=t; top++; } publicTPop() { top‐‐; returnvalues[top]; }  //ЭтотметодреализуетGetEnumeratorметод.Онпозволяет //операторуforeach использоватьобъектыкласса. publicIEnumerator<T>GetEnumerator()
56789
0;
18
{ for(intindex=top‐1;index>=0;index‐‐) { yieldreturnvalues[index]; } }  IEnumeratorIEnumerable.GetEnumerator() { returnGetEnumerator(); }  publicIEnumerable<T>TopToBottom { get{returnthis;} }  publicIEnumerable<T>BottomToTop { get { for(intindex=0;index<=top‐1;index++) { yieldreturnvalues[index]; } } }  publicIEnumerable<T>TopN(intitemsFromTop) { //Возвращаетзаданноечислосвершины. intstartIndex=itemsFromTop>=top?0:top‐itemsFromTop;  for(intindex=top‐1;index>=startIndex;index‐‐) { yieldreturnvalues[index]; } } }
Итератор может быть методом или методом доступа get. Итератор не может использоваться в событии, конструкторе экземпляра, стати­ческом конструкторе или статическом методе завершения.
19
Должно существовать неявное преобразование типа выражения в операторе yield return в аргумент типа для IEnumerable<T>, возвра­щаемого итератором. В C# метод итератора не может иметь параметры in, ref или out. В C# yield не является зарезервированным словом и имеет специальное значение, только если используется перед ключе­вым словом return или break.
Хотя итератор создается как метод, компилятор переводит его во вложенный Этот класс отслеживает положение итератора, пока в клиентском коде выполняется цикл foreach.
1. Что такое итератор?
2. В чем особенности метода итератора?
3. Значение какого типа должен возвращать метод итератора?
4. Что такое отношение универсальный итератор?
5. Для чего используется слово yield?
класс, который фактически является конечным автоматом.
КОНТРОЛЬНЫЕ ВОПРОСЫ
ДЕЛЕГАТЫ И ЛЯМБДА-ВЫРАЖЕНИЯ
В методах и методах расширения универсальных коллекций в ка­честве параметров широко используются параметры типов универ­сальных делегатов классов Action<>, Func<>, Predicate<>. Через эти параметры с помощью делегатов мы можем передать в метод требуе­мую нам функцию для настройки работы метода на особенности реша­емой нами задачи.
Делегаты и лямбда-выражения тесно связаны между где в качестве параметра метода коллекции используется делегат, в метод при его вызове может быть передано лямбда-выражение.
собой. Везде,
ДЕЛЕГАТЫ
Делегат – это тип, экземпляры которого представляют ссылки на методы с определенным списком параметров и типом возвращаемого значения. При создании экземпляра делегата этот экземпляр можно
20
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]