Полное руководство. С# 4.0 - Шилдт Герберт
Шрифт:
Интервал:
Закладка:
Сохраняет пары "ключ-значение”, а значит, реализует параллельный словарьConcurrentQueue Реализует параллельную очередь и соответствующий вариантинтерфейса IProducerConsumerCollectionConcurrentStack Реализует параллельный стек и соответствующий вариант интерфейса IproducerConsumerCollectionКак видите, в нескольких классах параллельных коллекций реализуется интерфейс IProducerConsumerCollection. Этот интерфейс также определен в пространстве имен System.Collections.Concurrent. Он служит в качестве расширенияинтерфейсов IEnumerable, IEnumerable и ICollection. Кроме того, в немопределены методы TryAdd() и TryTake(), поддерживающие шаблон "поставщик-потребитель". (Классический шаблон "поставщик-потребитель" отличается решениемдвух задач. Первая задача производит элементы коллекции, а другая потребляет их.)Метод TryAdd() пытается добавить элемент в коллекцию, а метод TryTake() — удалить элемент из коллекции. Ниже приведены формы объявления обоих методов.bool TryAdd(Т item)bool TryTake(out T item)Метод TryAdd() возвращает логическое значение true, если в коллекцию добавленэлемент item. А метод TryTake() возвращает логическое значение true, если элементitem удален из коллекции. Если метод TryAdd() выполнен успешно, то элемент itemбудет содержать объект. (Кроме того, в интерфейсе IProducerConsumerCollectionуказывается перегружаемый вариант метода СоруТо(), определяемого в интерфейсеICollection, а также метода ToArray(), копирующего коллекцию в массив.)Параллельные коллекции зачастую применяются в комбинации с библиотекойраспараллеливания задач (TPL) или языком PLINQ. В силу особого характера этих коллекций все их классы не будут рассматриваться далее подробно. Вместо этого на конкретных примерах будет дан краткий обзор класса BlockingCollection. Усвоивосновы построения класса BlockingCollection, вы сможете без особого трударазобраться и в остальных классах параллельных коллекций.В классе BlockingCollection, по существу, реализуется блокирующая очередь. Это означает, что в такой очереди автоматически устанавливается ожидание любых попыток вставить элемент в коллекцию, когда она заполнена, а также попытокудалить элемент из коллекции, когда она пуста. Это идеальное решение для тех ситуаций, которые связаны с применением шаблона "поставщик-потребитель". В классе BlockingCollection реализуются интерфейсы ICollection, IEnumerable,IEnumerable, а также IDisposable.В классе BlockingCollection определяются следующие конструкторы.public BlockingCollection()public BlockingCollection(int boundedCapacity)public BlockingCollection(IProducerConsumerCollection collection)public BlockingCollection(IProducerConsumerCollection collection,int boundedCapacity)В двух первых конструкторах в оболочку класса BlockingCollection заключается коллекция, являющаяся экземпляром объекта типа ConcurrentQueue.А в двух других конструкторах можно указать коллекцию, которая должна быть положена в основу коллекции типа BlockingCollection. Если указывается параметрboundedCapacity, то он должен содержать максимальное количество объектов, которые коллекция должна содержать перед тем, как она окажется заблокированной. Еслиже параметр boundedCapacity не указан, то коллекция оказывается неограниченной.Помимо методов TryAdd() и TryTake(), определяемых параллельно с теми,что указываются в интерфейсе IProducerConsumerCollection, в классеBlockingCollection определяется также ряд собственных методов. Ниже представлены методы, которые будут использоваться в приведенных далее примерах.public void Add(T item)public T Take()Когда метод Add() вызывается для неограниченной коллекции, он добавляетэлемент item, в коллекцию и затем возвращает управление вызывающей части программы. А когда метод Add() вызывается для ограниченной коллекции, он блокирует доступ к ней, если она заполнена. После того как из коллекции будет удален одинэлемент или больше, указанный элемент item будет добавлен в коллекцию, и затемпроизойдет возврат из данного метода. Метод Таkе() удаляет элемент из коллекциии возвращает управление вызывающей части программы. (Имеются также вариантыобоих методов, принимающие в качестве параметра признак задачи как экземпляробъекта типа CancellationToken.)Применяя методы Add() и Таке(), можно реализовать простой шаблон"поставщик-потребитель", как показано в приведенном ниже примере программы.В этой программе создается поставщик, формирующий символы от А до Z, а также потребитель, получающий эти символы. При этом создается коллекция типаBlockingCollection, ограниченная 4 элементами.// Простой пример коллекции типа BlockingCollection.using System;using System.Threading.Tasks;using System.Threading;using System.Collections.Concurrent;class BlockingDemo {static BlockingCollection be;// Произвести и поставить символы от А до Z.static void Producer() {for(char ch = 'A'; ch <= 'Z'; ch++) {be.Add(ch);Console.WriteLine("Производится символ " + ch);}}// Потребить 26 символов.static void Consumer() {for(int i=0; i < 26; i++)Console.WriteLine("Потребляется символ " + bc.Take());}static void Main() {// Использовать блокирующую коллекцию, ограниченную 4 элементами.be = new BlockingCollection(4);// Создать задачи поставщика и потребителя.Task Prod = new Task(Producer);Task Con = new Task(Consumer);// Запустить задачи.Con.Start();Prod.Start();// Ожидать завершения обеих задач.try {Task.WaitAll(Con, Prod);} catch(AggregateException exc) {Console.WriteLine(exc);} finally {Con.Dispose();Prod.Dispose();bc.Dispose();}}}Если запустить эту программу на выполнение, то на экране появится смешанныйрезультат, выводимый поставщиком и потребителем. Отчасти это объясняется тем,что коллекция bc ограничена 4 элементами, а это означает, что в нее может быть добавлено только четыре элемента, прежде чем ее придется сократить. В качестве эксперимента попробуйте сделать коллекцию bc неограниченной и понаблюдайте за полученными результатами. В некоторых средах выполнения это приведет к тому, чтовсе элементы коллекции будут сформированы до того, как начнется какое-либо их потребление. Кроме того, попробуйте ограничить коллекцию одним элементом. В этомслучае одновременно может быть сформирован лишь один элемент.Для работы с коллекцией типа BlockingCollection может оказаться полезным и метод CompleteAdding(). Ниже приведена форма его объявления.public void CompleteAdding()Вызов этого метода означает, что в коллекцию не будет больше добавлено ни одного элемента. Это приводит к тому, что свойство IsAddingComplete принимает логическое значение true. Если же коллекция пуста, то свойство IsCompleted принимаетлогическое значение true, и в этом случае вызовы метода Таке() не блокируются.Ниже приведены формы объявления свойств IsAddingComplete и IsCompleted.public bool IsCompleted { get; }public bool IsAddingComplete { get; }Когда коллекция типа BlockingCollection только начинает формироваться, эти свойства содержат логическое значение false. А после вызова методаCompleteAdding() они принимают логическое значение true.Ниже приведен вариант предыдущего примера программы, измененный с цельюпродемонстрировать применение метода CompleteAdding(), свойства IsCompletedи метода TryTake().// Применение методов CompleteAdding(), TryTake() и свойства IsCompleted.using System;using System.Threading.Tasks;using System.Threading;using System.Collections.Concurrent;class BlockingDemo {static BlockingCollection bc;// Произвести и поставить символы от А до Z.static void Producer() {for(char ch = 'A'; ch <= 'Z'; ch++) {bc.Add(ch);Console.WriteLine("Производится символ " + ch);}bc.CompleteAdding();}// Потреблять символы до тех пор, пока их будет производить поставщик.static void Consumer() {char ch;while(!bc.IsCompleted) {if(bc.TryTake(out ch))Console.WriteLine("Потребляется символ " + ch);}}static void Main() {// Использовать блокирующую коллекцию, ограниченную 4 элементами.bc = new BlockingCollection(4);// Создать задачи поставщика и потребителя.Task Prod = new Task(Producer);Task Con = new Task(Consumer);// Запустить задачи.Con.Start();Prod.Start();// Ожидать завершения обеих задач.try {Task.WaitAll(Con, Prod);} catch(AggregateException exc) {Console.WriteLine(exc);} finally {Con.Dispose();Prod.Dispose();bc.Dispose();}}}Этот вариант программы дает такой же результат, как и предыдущий. Главноеего отличие заключается в том, что теперь метод Producer() может производитьи поставлять сколько угодно элементов. С этой целью он просто вызывает методCompleteAdding(), когда завершает создание элементов. А метод Consumer() лишь"потребляет" произведенные элементы до тех пор, пока свойство IsCompleted непримет логическое значение true.Несмотря на специфический до некоторой степени характер параллельных коллекций, предназначенных в основном для параллельного программирования, у них,тем не менее, имеется немало общего с обычными, непараллельными коллекциями, описанными в предыдущих разделах. Если же вам приходится работать в средепараллельного программирования, то для организации одновременного доступа кданным из нескольких потоков вам, скорее всего, придется воспользоваться параллельными коллекциями.Сохранение объектов, определяемыхпользователем классов, в коллекцииРади простоты приведенных выше примеров в коллекции, как правило, сохранялись объекты встроенных типов, в том числе int, string и char. Но ведь в коллекцииможно хранить не только объекты встроенных типов. Достоинство коллекций в том исостоит, что в них допускается хранить объекты любого типа, включая объекты определяемых пользователем классов.Рассмотрим сначала простой пример применения класса необобщенной коллекции ArrayList для хранения информации о товарных запасах. В этом классе инкапсулируется класс Inventory.// Простой пример коллекции товарных запасов.using System;using System.Collections;class Inventory {string name;double cost;int onhand;public Inventory(string n, double c, int h) {name = n;cost = c;onhand = h;}public override string ToString() {returnString.Format("{0,-10}Стоимость: {1,6:С} Наличие: {2}",name, cost, onhand);}}class InventoryList {static void Main() {ArrayList inv = new ArrayList();// Добавить элементы в список.inv.Add(new Inventory("Кусачки", 5.95, 3));inv.Add(new Inventory("Отвертки", 8.29, 2));inv.Add(new Inventory("Молотки", 3.50, 4));inv.Add(new Inventory("Дрели", 19.88, 8));Console.WriteLine("Перечень товарных запасов:");foreach(Inventory i in inv) {Console.WriteLine(" " + i);}}}При выполнении программы из данного примера получается следующий результат.Перечень товарных запасов:Кусачки Стоимость: $5.95 Наличие: 3Отвертки Стоимость: $8.29 Наличие: 2Молотки Стоимость: $3.50 Наличие: 4Дрели Стоимость: $19.88 Наличие: 8Обратите внимание на то, что в данном примере программы не потребовалось никаких специальных действий для сохранения в коллекции объектов типа Inventory.Благодаря тому что все типы наследуют от класса object, в необобщенной коллекцииможно хранить объекты любого типа. Именно поэтому в необобщенной коллекциинетрудно сохранить объекты определяемых пользователем классов. Безусловно, этотакже означает, что такая коллекция не типизирована.Для того чтобы сохранить объекты определяемых пользователем классов в типизированной коллекции, придется воспользоваться классами обобщенных коллекций.В качестве примера ниже приведен измененный вариант программы из предыдущегопримера. В этом варианте используется класс обобщенной коллекции List, а результат получается таким же, как и прежде.// Пример сохранения объектов класса Inventory в// обобщенной коллекции класса List.using System;using System.Collections.Generic;class Inventory {string name;double cost;int onhand;public Inventory(string n, double c, int h) {name = n;cost = c;onhand = h;}public override string ToString() {returnString.Format("{0,-10}Стоимость: {1,6:С} Наличие: {2}",name, cost, onhand);}}class TypeSafeInventoryList {static void Main() {List inv = new List();// Добавить элементы в список.inv.Add(new Inventory("Кусачки", 5.95, 3));inv.Add(new Inventory("Отвертки", 8.29, 2));inv.Add(new Inventory("Молотки", 3.50, 4));inv.Add(new Inventory("Дрели", 19.88, 8));Console.WriteLine("Перечень товарных запасов:");foreach(Inventory i in inv) {Console.WriteLine(" " + i);}}}Данный пример отличается от предыдущего лишь передачей типа Inventory вкачестве аргумента типа конструктору класса List. А в остальном оба примерарассматриваемой здесь программы практически одинаковы. Это, по существу, означает, что для применения обобщенной коллекции не требуется никаких особых усилий,но при сохранении в такой коллекции объекта конкретного типа строго соблюдаетсятиповая безопасность.Тем не менее для обоих примеров рассматриваемой здесь программы характернаеще одна особенность: они довольно кратки. Если учесть, что для организации динамического массива, где можно хранить, извлекать и обрабатывать данные товарныхзапасов, потребуется не менее 40 строк кода, то преимущества коллекций сразу жестановятся очевидными. Нетрудно догадаться, что рассматриваемая здесь программаполучится длиннее в несколько раз, если попытаться закодировать все эти функцииколлекции вручную. Коллекции предлагают готовые решения самых разных задачпрограммирования, и поэтому их следует использовать при всяком удобном случае.У рассматриваемой здесь программы имеется все же один не совсем очевидныйнедостаток: коллекция не подлежит сортировке. Дело в том, что в классах ArrayListи List отсутствуют средства для сравнения двух объектов типа Inventory. Но изэтого положения имеются два выхода. Во-первых, в классе Inventory можно реализовать интерфейс IComparable, в котором определяется метод сравнения объектов данного класса. И во-вторых, для целей сравнения можно указать объект типа IComparer.Оба подхода рассматриваются далее по очереди.Реализация интерфейса IComparableЕсли требуется отсортировать коллекцию, состоящую из объектов определяемого пользователем класса, при условии, что они не сохраняются в коллекции класса SortedList, где элементы располагаются в отсортированном порядке, то в такойколлекции должен быть известен способ сортировки содержащихся в ней объектов.С этой целью можно, в частности, реализовать интерфейс IComparable для объектовсохраняемого типа. Интерфейс IComparable доступен в двух формах: обобщенной инеобобщенной. Несмотря на сходство применения обеих форм данного интерфейса,между ними имеются некоторые, хотя и небольшие, отличия, рассматриваемые ниже.Реализация интерфейса IComparable для необобщенных коллекцийЕсли требуется отсортировать объекты, хранящиеся в необобщенной коллекции, то для этой цели придется реализовать необобщенный вариант интерфейсаIComparable. В этом варианте данного интерфейса определяется только один методCompareTo(), который определяет порядок выполнения самого сравнения. Нижеприведена общая форма объявления метода CompareTo().int CompareTo(object obj)В методе CompareTo() вызывающий объект сравнивается с объектом obj. Для сортировки объектов по нарастающей конкретная реализация данного метода должнавозвращать нулевое значение, если значения сравниваемых объектов равны; положительное — если значение вызывающего объекта больше, чем у объекта obj; и отрицательное — если значение вызывающего объекта меньше, чем у объекта obj. А длясортировки по убывающей можно обратить результат сравнения объектов. Если жетип объекта obj не подходит для сравнения с вызывающим объектом, то в методеCompareTo() может быть сгенерировано исключение ArgumentException.В приведенном ниже примере программы демонстрируется конкретная реализация интерфейса IComparable. В этой программе интерфейс IComparable вводитсяв класс Inventory, разработанный в двух последних примерах из предыдущего раздела. В классе Inventory реализуется метод CompareTo() для сравнения полей nameобъектов данного класса, что дает возможность отсортировать товарные запасы по наименованию. Как показано в данном примере программы, коллекция объектов классаInventory подлежит сортировке благодаря реализации интерфейса IComparable вэтом классе.// Реализовать интерфейс IComparable.using System;using System.Collections;// Реализовать необобщенный вариант интерфейса IComparable.class Inventory : IComparable {string name;double cost;int onhand;public Inventory(string n, double c, int h) {name = n;cost = c;onhand = h;}public override string ToString() {returnString.Format("{0,-10(Стоимость: {1,6:С} Наличие: {2}",name, cost, onhand);}// Реализовать интерфейс IComparable.public int CompareTo(object obj) {Inventory b;b = (Inventory) obj;return name.CompareTo(b.name);}}class IComparableDemo {static void Main() {ArrayList inv = new ArrayList();// Добавить элементы в список.inv.Add(new Inventory("Кусачки", 5.95, 3));inv.Add(new Inventory("Отвертки", 8.29, 2));inv.Add(new Inventory("Молотки", 3.50, 4));inv.Add(new Inventory("Дрели", 19.88, 8));Console.WriteLine("Перечень товарных запасов до сортировки:");foreach(Inventory i in inv) {Console.WriteLine(" " + i);}Console.WriteLine();// Отсортировать список.inv.Sort();Console.WriteLine("Перечень товарных запасов после сортировки:");foreach(Inventory i in inv) {Console.WriteLine(" " + i);}}}Ниже приведен результат выполнения данной программы. Обратите внимание нато, что после вызова метода Sort() товарные запасы оказываются отсортированнымипо наименованию.Перечень товарных запасов до сортировки:Кусачки Стоимость: $5.95 Наличие: 3Отвертки Стоимость: $8.29 Наличие: 2Молотки Стоимость: $3.50 Наличие: 4Дрели Стоимость: $19.88 Наличие: 8Перечень товарных запасов после сортировки:Дрели Стоимость: $19.88 Наличие: 8Кусачки Стоимость: $5.95 Наличие: 3Молотки Стоимость: $3.50 Наличие: 4Отвертки Стоимость: $8.29 Наличие: 2Реализация интерфейса IComparable для обобщенных коллекцийЕсли требуется отсортировать объекты, хранящиеся в обобщенной коллекции, то для этой цели придется реализовать обобщенный вариант интерфейсаIComparable. В этом варианте интерфейса IComparable определяется приведенная ниже обобщенная форма метода CompareTo().int CompareTo (Т other)В методе CompareTo() вызывающий объект сравнивается с другим объектомother. Для сортировки объектов по нарастающей конкретная реализация данногометода должна возвращать нулевое значение, если значения сравниваемых объектовравны; положительное — если значение вызывающего объекта больше, чем у объектадругого other; и отрицательное — если значение вызывающего объекта меньше, чему другого объекта other. А для сортировки по убывающей можно обратить результатсравнения объектов. При реализации обобщенного интерфейса IComparable имятипа реализующего класса обычно передается в качестве аргумента типа.Приведенный ниже пример программы является вариантом предыдущего примера, измененным с целью реализовать и использовать обобщенный интерфейсIComparable<Т>. Обратите внимание на применение класса обобщенной коллекцииList вместо класса необобщенной коллекции ArrayList.// Реализовать интерфейс IComparable.using System;using System.Collections.Generic;// Реализовать обобщенный вариант интерфейса IComparable.class Inventory : IComparable {string name;double cost;int onhand;public Inventory(string n, double c, int h) {name = n;cost = c;onhand = h;}public override string ToString() {returnString.Format("{0,-10}Стоимость: {1,6:С} Наличие: {2}",name, cost, onhand);}// Реализовать интерфейс IComparable.public int CompareTo(Inventory obj) {return name.CompareTo(obj.name);}}class GenericIComparableDemo {static void Main() {List inv = new List();// Добавить элементы в список.inv.Add(new Inventory("Кусачки", 5.95, 3));inv.Add(new Inventory("Отвертки", 8.2 9, 2));inv.Add(new Inventory("Молотки", 3.50, 4));inv.Add(new Inventory("Дрели", 19.88, 8));Console.WriteLine("Перечень товарных запасов до сортировки:");foreach(Inventory i in inv) {Console.WriteLine(" " + i) ;}Console.WriteLine();// Отсортировать список.inv.Sort();Console.WriteLine("Перечень товарных запасов после сортировки:");foreach(Inventory i in inv) {Console.WriteLine(" " + i);}}}Эта версия программы дает такой же результат, как и предыдущая, необобщеннаяверсия.Применение интерфейса IComparerДля сортировки объектов определяемых пользователем классов зачастую прощевсего реализовать в этих классах интерфейс IComparable. Тем не менее данную задачуможно решить и с помощью интерфейса IComparer. Для этой цели необходимо сначала создать класс, реализующий интерфейс IComparer, а затем указать объект этогокласса, когда потребуется сравнение.Интерфейс IComparer существует в двух формах: обобщенной и необобщенной.Несмотря на сходство применения обеих форм данного интерфейса, между ними имеются некоторые, хотя и небольшие, отличия, рассматриваемые ниже.Применение необобщенного интерфейса IComparerВ необобщенном интерфейсе IComparer определяется только один метод,Compare().int Compare(object x, object y)В методе Compare() сравниваются объекты x и у. Для сортировки объектов по нарастающей конкретная реализация данного метода должна возвращать нулевое значение, если значения сравниваемых объектов равны; положительное — если значениеобъекта х больше, чем у объекта у; и отрицательное — если значение объекта х меньше, чем у объекта у. А для сортировки по убывающей можно обратить результат сравнения объектов. Если же тип объекта х не подходит для сравнения с объектом у, то вметоде CompareTo() может быть сгенерировано исключение ArgumentException.Объект типа IComparer может быть указан при конструировании объекта классаSortedList, при вызове метода ArrayList.Sort(IComparer), а также в ряде другихмест в классах коллекций. Главное преимущество применения интерфейса IComparerзаключается в том, что сортировке подлежат объекты тех классов, в которых интерфейсIComparable не реализуется.Приведенный ниже пример программы является вариантом рассматривавшегосяранее необобщенного примера программы учета товарных запасов, переделанного сцелью воспользоваться интерфейсом IComparer для сортировки перечня товарных запасов. В этом варианте программы сначала создается класс CompInv, в котором реализуется интерфейс IComparer и сравниваются два объекта класса Inventory. А затемобъект класса CompInv указывается в вызове метода Sort() для сортировки перечнятоварных запасов.// Использовать необобщенный вариант интерфейса IComparer.using System;using System.Collections;// Создать объект типа IComparer для объектов класса Inventory.class CompInv : IComparer {// Реализовать интерфейс IComparer.public int Compare(object x, object y) {Inventory, a, b;a = (Inventory) x;b = (Inventory) y;return string.Compare(a.name, b.name, StringComparison.Ordinal);}}class Inventory {public string name;double cost;int onhand;public Inventory(string n, double c, int h) {name = n;cost = c;onhand = h;}public override string ToString() {returnString.Format("{0,-10} Цена: {1,6:С} В наличии: {2}",name, cost, onhand);}}class IComparerDemo {static void Main() {CompInv comp = new CompInv();ArrayList inv = new ArrayList();// Добавить элементы в список.inv.Add(new Inventory("Кусачки", 5.95, 3));inv.Add(new Inventory("Отвертки", 8.29, 2));inv.Add(new Inventory("Молотки", 3.50, 4));inv.Add(new Inventory ("Дрели", 19.88, 8));Console.WriteLine("Перечень товарных запасов до сортировки:");foreach(Inventory i in inv) {Console.WriteLine(" " + i);}Console.WriteLine();// Отсортировать список, используя интерфейс IComparer.inv.Sort(comp);Console.WriteLine("Перечень товарных запасов после сортировки:");foreach(Inventory i in inv) {Console.WriteLine(" " + i);}}}Эта версия программы дает такой же результат, как и предыдущая.Применение обобщенного интерфейса IComparerИнтерфейс IComparer является обобщенным вариантом интерфейса IComparer.В нем определяется приведенный ниже обобщенный вариант метода Compare().int Compare(Т х, Т у)В этом методе сравниваются объекты х и у и возвращается нулевое значение, еслизначения сравниваемых объектов равны; положительное — если значение объекта хбольше, чем у объекта у; и отрицательное — если значение объекта х меньше, чем уобъекта у.Ниже приведена обобщенная версия предыдущей программы учета товарных запасов, в которой теперь используется интерфейс IComparer. Она дает такой жерезультат, как и необобщенная версия этой же программы.// Использовать обобщенный вариант интерфейса IComparer.using System;using System.Collections.Generic;// Создать объект типа IComparer для объектов класса Inventory.class CompInv : IComparer where T : Inventory {// Реализовать интерфейс IComparer.public int Compare(T x, T y) {return string.Compare(x.name, y.name, StringComparison.Ordinal);}}class Inventory {public string name;double cost;int onhand;public Inventory(string n, double c, int h) {name = n;cost = c;onhand = h;}public override string ToString() {returnString.Format("{0,-10} Цена: {1,6:С} В наличии: {2}",name, cost, onhand);}}class GenericIComparerDemo {static void Main() {CompInv comp = new CompInv();List inv = new List();// Добавить элементы в список.inv.Add(new Inventory("Кусачки", 5.95, 3));inv.Add(new Inventory("Отвертки", 8.29, 2));inv.Add(new Inventory("Молотки", 3.50, 4));inv.Add(new Inventory("Дрели", 19.88, 8));Console.WriteLine("Перечень товарных запасов до сортировки:");foreach(Inventory i in inv) {Console.WriteLine(" " + i);}Console.WriteLine ();// Отсортировать список, используя интерфейс IComparer.inv.Sort(comp);Console.WriteLine("Перечень товарных запасов после сортировки:");foreach(Inventory i in inv) {Console.WriteLine(" " + i);}}}Применение класса StringComparerВ простых примерах из этой главы указывать явно способ сравнения символьныхстрок совсем не обязательно. Но это может потребоваться в тех случаях, когда строкисохраняются в отсортированной коллекции или когда строки ищутся либо сортируются в коллекции. Так, если строки должны быть отсортированы с учетом настроекодной культурной среды, а затем их приходится искать с учетом настроек другой культурной среды, то во избежание ошибок, вероятнее всего, потребуется указать способсравнения символьных строк. Аналогичная ситуация возникает и при хешированииколлекции. Для подобных (и других) случаев в конструкторах классов некоторых коллекций предусмотрена поддержка параметра типа IComparer. С целью явно указатьспособ сравнения символьных строк этому параметру передается в качестве аргументаэкземпляр объекта класса StringComparer.Класс StringComparer был подробно описан в главе 21 при рассмотрении вопросовсортировки и поиска в массивах. В этом классе реализуются интерфейсы IComparer,IComparer, IEqualityComparer, а также IEqualityComparer.Следовательно, экземпляр объекта типа StringComparer может быть передан параметру типа IComparer в качестве аргумента. В классе StringComparer определяется несколько доступных только для чтения свойств, возвращающих экземпляробъекта типа StringComparer, который поддерживает различные способы сравнения символьных строк. Как пояснялось в главе 21, к числу этих свойств относятсяследующие: CurrentCulture, CurrentCultureIgnoreCase, InvariantCulture,InvariantCultureIgnoreCase, Ordinal, а также OrdinalIgnoreCase. Все эти свойства можно использовать для явного указания способа сравнения символьньгх строк.В качестве примера ниже показано, как коллекция типа SortedList конструируется для хранения символьных строк, ключи которых сравниваются порядковым способом.SortedList users =new SortedList(StringComparer.Ordinal);Доступ к коллекции с помощью перечислителяК элементам коллекции нередко приходится обращаться циклически, например,для отображения каждого элемента коллекции. С этой целью можно, с одной стороны, организовать цикл foreach, как было показано в приведенных выше примерах,а с другой — воспользоваться перечислителем. Перечислитель — это объект, которыйреализует необобщенный интерфейс IEnumerator или обобщенный интерфейсIEnumerator<Т>.В интерфейсе IEnumerator определяется одно свойство, Current, необобщеннаяформа которого приведена ниже.object Current { get; }А в интерфейсе IEnumerator объявляется следующая обобщенная формасвойства Current.Т Current { get; }В обеих формах свойства Current получается текущий перечисляемый элементколлекции. Но поскольку свойство Current доступно только для чтения, то перечислитель может служить только для извлечения, но не видоизменения объектов в коллекции.В интерфейсе IEnumerator определяются два метода. Первым из них является метод MoveNext(), объявляемый следующим образом.bool MoveNext()При каждом вызове метода MoveNext() текущее положение перечислителя смещается к следующему элементу коллекции. Этот метод возвращает логическое значение true, если следующий элемент коллекции доступен, и логическое значение false,если достигнут конец коллекции. Перед первым вызовом метода MoveNext() значениесвойства Current оказывается неопределенным. (В принципе до первого вызова метода MoveNext() перечислитель обращается к несуществующему элементу, которыйдолжен находиться перед первым элементом коллекции. Именно поэтому приходитсявызывать метод MoveNext(), чтобы перейти к первому элементу коллекции.)Для установки перечислителя в исходное положение, соответствующее началу коллекции, вызывается приведенный ниже метод Reset().void Reset()После вызова метода Reset() перечисление вновь начинается с самого начала коллекции. Поэтому, прежде чем получить первый элемент коллекции, следует вызватьметод MoveNext().В интерфейсе IEnumerator методы MoveNext() и Reset() действуют по томуже самому принципу.Необходимо также обратить внимание на два следующих момента. Во-первых,перечислитель нельзя использовать для изменения содержимого перечисляемой сего помощью коллекции. Следовательно, перечислители действуют по отношению кколлекции как к доступной только для чтения. И во-вторых, любое изменение в перечисляемой коллекции делает перечислитель недействительным.Применение обычного перечислителяПрежде чем получить доступ к коллекции с помощью перечислителя, необходимо получить его. В каждом классе коллекции для этой цели предоставляется методGetEnumerator(), возвращающий перечислитель в начало коллекции. Используяэтот перечислитель, можно получить доступ к любому элементу коллекции по очереди. В целом, для циклического обращения к содержимому коллекции с помощьюперечислителя рекомендуется придерживаться приведенной ниже процедуры.