Для установки перечислителя в исходное положение, соответствующее началу коллекции, вызывается приведенный ниже метод Reset():
Reset()
После вызова метода Reset() перечисление вновь начинается с самого начала коллекции. Поэтому, прежде чем получить первый элемент коллекции, следует вызвать метод MoveNext(). [2]
В интерфейсе IEnumerator<T> методы MoveNext() и Reset() действуют
по тому же самому принципу. Необходимо также обратить внимание на два следующих
момента. Во-первых, перечислитель нельзя использовать для изменения содержимого
перечисляемой с его помощью коллекции. Следовательно, перечислители действуют
по отношению к коллекции как к доступной только для чтения. И во-вторых, любое
изменение в перечисляемой коллекции делает перечислитель недействительным.
.1 Применение обычного перечислителя
Прежде чем получить доступ к коллекции с помощью перечислителя, необходимо получить его. В каждом классе коллекции для этой цели предоставляется метод GetEnumerator(), возвращающий перечислитель в начало коллекции. Используя этот перечислитель, можно получить доступ к любому элементу коллекции по очереди. В целом, для циклического обращения к содержимому коллекции с помощью перечислителя рекомендуется придерживаться приведенной ниже процедуры:
) Получить перечислитель, устанавливаемый в начало коллекции, вызвав для этой коллекции метод GetEnumerator(). [2]
) Организовать цикл, в котором вызывается метод MoveNext(). Повторять цикл до тех пор, пока метод MoveNext() возвращает логическое значение true.
) Получить в цикле каждый элемент коллекции с помощью свойства Current. [2]
Пример использования перечислителей, заимствованный из источника [2] показан в листинге 8 (см. Приложение А).
Для циклического обращения к элементам коллекции цикл fоreach оказывается
более удобным, чем перечислитель. Тем не менее, перечислитель предоставляет
больше возможностей для управления, поскольку его можно при желании всегда
установить в исходное положение.
9.2 Применение перечислителя типа IDictionaryEnumerator
Если для организации коллекции в виде словаря, например типа Hashtable, реализуется необобщенный интерфейс IDictionary, то для циклического обращения к элементам такой коллекции следует использовать перечислитель типа IDictionaryEnumerator вместо перечислителя типа IEnumerator. Интерфейс IDictionaryEnumerator наследует от интерфейса IEnumerator и имеет три дополнительных свойства. Первым из них является приведенное ниже свойство. [3]
Entry {get;}
Свойство Entry позволяет получить пару "ключ-значение" из перечислителя в форме структуры DictionaryEntry. Напомним, что в структуре DictionaryEntry определяются два свойства, Key и Value, с помощью которых можно получать доступ к ключу или значению, связанному с элементом коллекции. Ниже приведены два других свойства, определяемых в интерфейсе IDictionaryEnumerator
object Key { get; }
object Value { get; }
С помощью этих свойств осуществляется непосредственный доступ к ключу или значению. [2]
Перечислитель типа IDictionaryEnumerator используется аналогично обычному
перечислителю, за исключением того, что текущее значение в данном случае
получается с помощью свойств Entry, Key или Value, а не свойства Current.
Следовательно, приобретя перечислитель типа IDictionaryEnumerator, необходимо
вызвать метод MoveNext(), чтобы получить первый элемент коллекции. А для
получения остальных ее элементов следует продолжить вызовы метода MoveNext().
Этот метод возвращает логическое значение false, когда в коллекции больше нет
ни одного элемента. [2]
9.3 Реализация интерфейсов IEnumerable и IEnumerator
Для циклического обращения к элементам коллекции зачастую проще организовать цикл foreach, чем пользоваться непосредственно методами интерфейса IEnumerator. Тем не менее, ясное представление о принципе действия подобных интерфейсов важно иметь по еще одной причине: если требуется создать класс, содержащий объекты, перечисляемые в цикле foreach, то в этом классе следует реализовать интерфейсы IEnumerator и IEnumerable.
Ниже представлен пример реализации интерфейса IEnumerator
bool MoveNext()
{(index == ints.Length - 1)
{();false;
}++;true;
}
И пример реализации интерфейса Ienumerable
public IEnumerator GetEnumerator()
{this;
}
Иными словами, для того чтобы обратиться к объекту определяемого
пользователем класса в цикле foreach, необходимо реализовать интерфейсы
IEnumerator и IEnumerable в их обобщенной или необобщенной форме.
10. Итераторы и оператор yield
Итератор представляет собой метод, в котором используется ключевое слово yield для перебора по коллекции или массиву. [3]
Наиболее простой способ создания итератора заключается в реализации метода GetEnumerator для интерфейса IEnumerable, например:
System.Collections.IEnumerator GetEnumerator()
{(int i = 0; i < 10; i++)
{return i;
}
}
Наличие метода GetEnumerator создает тип перечисляемого типа и позволяет
использовать оператор foreach statement. Если бы приведенный выше метод был
частью определения класса для ListClass, то можно было бы использовать foreach
для класса следующим образом [5]
static void Main()
{listClass1 = new ListClass();
(int i in listClass1)
{.Console.WriteLine(i);
}
}
Оператор foreach вызывает ListClass.GetEnumerator() и использует возвращенный перечислитель для итерации значений. [5]
Кроме того, можно использовать именованные итераторы в поддержку различных возможностей перебора одной и той же коллекции данных. [5]
Например, можно было бы предоставить один итератор, возвращающий элементы по возрастанию, а другой итератора, возвращающий элементы по убыванию. Итератор может также иметь параметры, позволяющие клиентам управлять всем поведением итератора или его часть. Следующий итератор реализует интерфейс IEnumerable при помощи именованного итератора SampleIterator [5]
System.Collections.IEnumerable SampleIterator(int start, int end)
{(int i = start; i <= end; i++)
{return i;
}
}
Именованный итератор вызывается следующим образом. [5]
test = new ListClass();(int n in test.SampleIterator(1, 10))
{.Console.WriteLine(n);
}
В одном итераторе можно использовать несколько операторов yield, как в
следующем примере. [5]
public System.Collections.IEnumerator GetEnumerator()
{return "With an iterator, ";return "more than one ";return "value can be returned";
yield return ".";
}
Результаты можно вывести с помощью оператора foreach. [5]
(string element in new TestClass())
{.Console.Write(element);
}
В каждой последовательной итерации цикла foreach (или прямом вызове IEnumerator.MoveNext) следующий текст кода итератора возобновляется после оператора yield и продолжается до конца текста итератора или до оператора yield break.
Итераторы не поддерживают метод IEnumeratorReset(). Для повторной итерации сначала необходимо получить новый итератор. [5]
IEnumerable.GetEnumerator()
{(int i = 0; i < books.Length; i++)
{return books[i];
}
}
Метод GetEnumerator() теперь будет являться итератором. Когда будет осуществляться перебор в объекте Library в цикле foreach, то будет идти обращение к вызову yield return books[i]. При обращении к оператору yield return будет сохраняться текущее местоположение. И когда метод foreach перейдет к следующей итерации для получения нового объекта, итератор начнет выполнения с этого местоположения. [3]
В основной программе в цикле foreach выполняется перебор, благодаря реализации итератора:
(Book b in library)
{.WriteLine(b.Name);
}
При реализации итератора в методе GetEnumerator() применять перебор массива в цикле for необязательно. Можно определить несколько вызовов оператора yield return, как показано в примере ниже. [3]
IEnumerable.GetEnumerator()
{return books[0];return books[1];return books[2];
}
В этом случае при каждом вызове оператора yield return итератор также
будет запоминать текущее местоположение и при последующих вызовах начинать с
него. [3]
10.1 Именованный итератор
Выше для создания итератора был использован метод GetEnumerator. Но оператор yield можно использовать внутри любого метода, только такой метод должен возвращать объект интерфейса IEnumerable. Подобные методы еще называют именованными итераторами. [3]
В листинге 10 (см. Приложение А) приведен пример из источника [3], в котором создается именованный итератор в классе Library.
Определенный здесь итератор - метод IEnumerable GetBooks(int max) в качестве параметра принимает количество выводимых объектов.
IEnumerable GetBooks(int max)
{(int i = 0; i < max; i++)
{(i == books.Length)
{break;
}
{return books[i];
}
}
}
В процессе работы программы может сложиться, что его значение будет больше, чем длина массива books. И чтобы не произошло ошибки, используется оператор yield break. Этот оператор прерывает выполнение итератора. [3]
Применение итератора:
library = new Library();(Book b in library.GetBooks(5))
{.WriteLine(b.Name);
}
Вызов library.GetBooks(5) будет возвращать набор из не более чем 5 объектов Book. Но так как в примере всего три таких объекта, то в методе GetBooks после трех операций сработает оператор yield break. [3]
(i == books.Length)
{break;
}
Именованные итераторы оказываются весьма полезными в некоторых ситуациях, поскольку они позволяют передавать аргументы итератору, управляющему процессом получения конкретных элементов из коллекции. Например, итератору можно передать начальный и конечный пределы совокупности элементов, возвращаемых из коллекции итератором.
Эту форму итератора можно перегрузить, расширив ее функциональные возможности. В приведенном ниже примере программы демонстрируется способ применения именованного итератора для получения элементов коллекции. В этом примере элементы перечисляются в заданных начальном и конечном пределах. [5]
System;System.Collections;MyClass
{ch = 'A';
// Этот итератор возвращает буквы английского алфавита,
}
}
Коллекции служат хранилищем объектов и обеспечивают доступ к ним. Использование коллекций в программировании имеет много преимуществ. С их помощью уменьшается количество написанного кода. Над коллекциями можно производить разные операции. В них можно добавлять различные элементы, изменять имеющиеся или удалять. В курсовой работе были приведены примеры из различных источников, в которых можно было наблюдать перечисленные операции.
Коллекции значительно упрощают написанные программы. Их размер динамичен,
что является одним из важных плюсов коллекций. Еще один плюс коллекций - в них
уже имеется внутренняя реализация различных структур данных, начиная со списков
и заканчивая хеш-таблицами. Методы поиска, удаления или добавления элементов в
структуру данных уже реализованы внутри коллекции, поэтому в большинстве
случаев не требуется самостоятельно реализовывать их.
1. Чистяков, В. Коллекции в .NET Framework Class Library / В. Чистяков // RSDN Magazine. - 2003. № 6. - С. 31-34.
. Нейгел, Кристиан C# 5.0 и платформа .Net 4.5 для профессионалов / Кристиан Нейгел [и др.].; пер. с англ. Ю.Н. Артеменко - М.: ООО "И.Д. Вильямс", 2014. - 1440 с.
3. Троелсен,
Эндрю Язык программирования C# 5.0 и платформа .NET 4.5 / Эндрю Троелсен; пер.
с англ. Ю. Н. Артеменко. - 6-е изд. - М.: ООО "И.Д. Вильямс", 2013. -
1311 с.
Листинги и результаты работы программ
В листинге 1 представлен пример использования коллекций, работы с элементами, добавление и удаление некоторых элементов.
Листинг 1 - Создание и применение двух коллекций
using System;System.Collections;System.Collections.Generic;
Collections
{Program
{void Main(string[] args)
{
// необобщенная коллекция ArrayListobjectList = new ArrayList() { 1, 2, "string", 'c', 2.0f };
obj = 45.8;.Add(obj);.Add("string2");.RemoveAt(0); // удаление первого элемента
(object o in objectList)
{.WriteLine(o);
}.WriteLine("Общее число элементов коллекции: " + objectList.Count);
// обобщенная коллекция List
<string> countries = new List<string>() { "Россия", "США", "Великобритания", "Китай" };
.Add("Франция");.RemoveAt(1); // удаление второго элемента(string s in countries)
{.WriteLine(s);
}.ReadLine();
}
}
}
Результат работы программы представлен на рисунке А1.
Рисунок А.1 - Создание и применение коллекций ArrayList и List
Листинг 2 - Реализация списка List<T>
using System;System.Collections.Generic;
Collections
{Program
{void Main(string[] args)
{<int> numbers = new List<int>() { 1, 2, 3, 45 };.Add(6); // добавление элемента
.AddRange(new int[] { 7, 8, 9 });
.Insert(0, 666); // вставляем на первое
место в списке число 666
numbers.RemoveAt(1); // удаляем второй элемент
(int i in numbers)
{.WriteLine(i);
}
<Person> persons = new List<Person>(3);.Add(new Person() { Name = "Том" });.Add(new Person() { Name = "Билл" });
(Person p in persons)
{.WriteLine(p.Name);
}
.ReadLine();
}
}
Person
{string Name { get; set; }
}
}
Листинг 3 - Использование очереди Queue<Т>
System;System.Collections.Generic;
Collections
{Program
{void Main(string[] args)
{<int> numbers = new Queue<int>();
.Enqueue(3); // очередь 3.Enqueue(5); // очередь 3, 5.Enqueue(8); // очередь 3, 5, 8
// получаем первый элемент очереди
int queueElement = numbers.Dequeue(); //теперь очередь 5, 8.WriteLine(queueElement);
<Person> persons = new Queue<Person>();
.Enqueue(new Person() { Name = "Tom"
});.Enqueue(new Person() { Name = "Bill" });.Enqueue(new Person() {
Name = "John" });
// получаем первый элемент без его извлечения
Person pp = persons.Peek();.WriteLine(pp.Name);
.WriteLine("Сейчас в очереди {0} человек",
persons.Count);
// теперь в очереди Tom, Bill, John
foreach (Person p in persons)
{.WriteLine(p.Name);
}
// Извлекаем первый элемент в очереди - Tom
person = persons.Dequeue(); // теперь в очереди Bill, John
.WriteLine(person.Name);.ReadLine();
}
}
Person
{string Name { get; set; }
}
}
Результат работы программы можно увидеть на рисунке А.2.
Рисунок А.2 - Результат использование очереди Queue<Т>
Листинг 4 - Пример использования списка LinkedList<T>
System;System.Collections.Generic;
Collections
{Program
{void Main(string[] args)
{<int> numbers = new LinkedList<int>();
.AddLast(1); // вставляем узел со значением 1 на последнее место
// так как в списке нет узлов, то последнее будет также и первым
numbers.AddFirst(2); // вставляем узел со значением 2 на первое место
// теперь у нас список имеет следующую последовательность: 2, 1, 3
foreach (int i in numbers)
{.WriteLine(i);
}
<Person> persons = new LinkedList<Person>();
// добавляем persona в список и получим объект LinkedListNode<Person>, в котором хранится имя Tom
LinkedListNode<Person> tom = persons.AddLast(new Person() { Name = "Tom" });.AddLast(new Person() { Name = "John" });.AddFirst(new Person() { Name = "Bill" });
.WriteLine(tom.Previous.Value.Name); // получаем узел перед
томом и его значение.WriteLine(tom.Next.Value.Name); // получаем узел после тома и его значение
Console.ReadLine();
}
}
Person
{string Name { get; set; }
}
}
Результат работы программы представлен на рисунке А.3.
Рисунок А.3 - Результат использования списка LinkedList<T>
Листинг 5 - Пример использования словарей
System;System.Collections.Generic;
ConsoleApplication1
{UserInfo
{
// Метод, реализующий словарьstatic Dictionary<int, string> MyDic(int i)
{<int, string> dic = new Dictionary<int,string>();.WriteLine("Введите имя сотрудника: \n");
string s;(int j = 0; j < i; j++)
{.Write("Name{0} --> ",j);= Console.ReadLine();.Add(j, s);.Clear();
}dic;
}
}Program
{void Main()
{
Console.Write("Сколько сотрудников добавить? ");
{i = int.Parse(Console.ReadLine());<int, string> dic = UserInfo.MyDic(i);
// Получить коллекцию ключей<int> keys = dic.Keys;
.WriteLine("База данных содержит: ");
foreach (int j in keys).WriteLine("ID -> {0} Name -> {1}",j,dic[j]);
}(FormatException)
{.WriteLine("Неверный ввод");
}.ReadLine();
}
}
}
Результат работы программы представлен на рисунке А.4.
Рисунок А.4 - Пример использования словарей
Листинг 6 - Пример использования класса SortedSet<T>
System;System.Collections.Generic;
ConsoleApplication1
{Program
{void Main()
{
// Создадим два множества<char> ss = new SortedSet<char>();<char> ss1 = new SortedSet<char>();.Add('A');.Add('B');.Add('C');.Add('Z');
ShowColl(ss, "Первая коллекция: ");.Add('X');
ss1.Add('Y');.Add('Z');(ss1, "Вторая коллекция");
.SymmetricExceptWith(ss1);
ShowColl(ss,"Исключили разноименность двух множеств: ");.UnionWith(ss1);(ss, "Объединение множеств: ");
.ExceptWith(ss1);(ss, "Вычитание множеств");
Console.ReadLine();
}void ShowColl(SortedSet<char> ss, string s)
{.WriteLine(s);(char ch in ss).Write(ch + " ");.WriteLine("\n");
}
}
}
Результат работы программы представлен на рисунке А.5.