Видео курс Шаблоны проектирования

Основные темы, рассматриваемые на уроке:
00:28 1 Метафора
06:14 2 Частота использования и важные интересы
06:52 3 Программный код метафоры
08:30 4 Понятие контейнера и компонента
10:30 5 Реализация интерфейса ICollection
12:30 6 Оператор yield
14:06 7 Классическое (GOF) представление паттерна
15:01 8 Представление Microsoft .net
15:53 9 Структура паттерна на языке C# (Классическое)
17:24 10 Структура паттерна на языке C# (Microsoft .net)
20:48 11 Внутренний и внешний итератор
23:08 12 Назначение паттерна
25:44 13 Использование паттерна

Видео урок посвящен шаблону проектирования Iterator (Итератор), который представляет удобный и безопасный способ доступа к элементам коллекции (составного объекта), при этом не раскрывая внутреннего представления этой коллекции.

Для просмотра полной версии видеокурса и получения доступа к дополнительным учебным материалам Вам необходимо оформить подписку
Оформить подписку

Паттерн Iterator

Название

Итератор

Также известен как

Cursor (Курсор)

Классификация

По цели: поведенческий

По применимости: к объектам

Частота использования

Высокая                -   1 2 3 4 5

Назначение

Паттерн Iterator - предоставляет удобный и безопасный способ доступа к элементам коллекции (составного объекта), при этом не раскрывая внутреннего представления этой коллекции.

Введение

Что такое коллекция в объективной реальности? Коллекция (от происходит от латинского collectio - собирание, сбор) - систематизированное собрание чего-либо, объединённое по какому-то конкретному признаку, имеющее внутреннюю целостность и принадлежащее конкретному владельцу - частному лицу, организации, государству.

Предлагается рассмотреть коллекцию на примере банка, как коллекции денег и ценностей.

Исторически главной функцией банков было безопасное хранение денег клиентов. Во многих странах банки обязаны хранить тайну об операциях, счетах и вкладах клиентов. Может ли клиент банка самостоятельно войти в банковское хранилище и положить деньги на депозит или взять кредит? Конечно же нет. Получается, что клиент банка не имеет прямого доступа к элементам (деньгам) коллекции (банка).

Каким же способом клиент может положить деньги в банк или взять деньги из банка? Только через кассира. Кассир-операционист - лицо банка, ведь он первый, кто встречает посетителей и только он может получить доступ к хранилищу. Именно кассир осуществляет операции по приему, учету, выдаче и хранению денежных средств с обязательным соблюдением правил, обеспечивающих их сохранность.

Если перейти на язык информатики, то банк можно представить программной коллекцией, а кассира механизмом доступа (итератором) к элементам коллекции. Коллекция в программировании - это программный объект, содержащий в себе, набор элементов одного или различных типов. Итератор (iterator) - это объект, предоставляющий безопасный доступ к элементам коллекции.

В инфраструктуре .Net большинство объектов-коллекций реализует интерфейс IEnumerable, а объекты-итераторы называются «перечислителями» (enumerators) и реализуют интерфейс IEnumerator.

В случае с примером банка и кассира становится понятно, что банк является тем что перечисляют (перечисляемый - IEnumerable), а кассир является тем, кто перечисляет (перечислитель - IEnumerator) и идея использования техники «перечисляемый-перечислитель» была положена в основу паттерна Iterator.

Ниже представлен пример использования техники «перечисляемый-перечислитель» в интерпретации Microsoft (с использованием интерфейсов IEnumerable и IEnumerator).

См. пример к главе: \016_Iterator\000_ Bank

Воспринимать паттерн Iterator как шаблон для создания программных коллекций – это грубо и вульгарно прямолинейно. В действительности паттерн Iterator не описывает построение полноценной коллекции. Паттерн Iterator описывает только технику - «перечисляемый-перечислитель» которая лежит в основе понятия составного объекта.

Составным объектом же может являться, как программный контейнер, так и программная коллекция. Термины «коллекции» и «контейнеры» иногда программистами используются довольно свободно и взаимозаменяемо.

В информатике контейнер представляет собой разновидность (настраиваемый класс) коллекции. Контейнер содержит в себе специальные объекты-компоненты. Контейнер управляет внутренним взаимодействием компонентов друг с другом (внутри контейнера), а также контролирует взаимодействие внутренних компонентов с другими (внешними) объектами, находящимися за пределами контейнера. Другими словами, контейнер – это связующее звено между компонентами, содержащимися в этом контейнере и внешними объектами из кода приложения. Внешние объекты могут получить ссылки на компоненты, содержащиеся в контейнере, не зная настоящего имени этого компонента.

Для того чтобы правильно создать пользовательский контейнер и наполнить его компонентами, потребуется воспользоваться тремя интерфейсами IContainer, IComponent и ISite. Или воспользоваться напрямую или через наследование готовыми классами контейнера Container и компонентов Component, которые идут в поставке .Net Framework. Доступ к компонентам, содержащимся в контейнере, можно получить с помощью свойства ComponentCollection Components этого контейнера. 

Для лучшего понимания идеи совместного использования контейнера Container и компонентов Component предлагается воспользоваться метафорой. Контейнер - Container (IContainer) можно проассоциировать со страной, в которой проживает человек – Component (IComponent), у которого имеется паспорт - Site (ISite) в котором указано имя человека и гражданство (принадлежность к стране). 

Объект типа Site (ISite) предоставляет дополнительное удобство для работы с компонентами. Например, в давние времена не было паспортов и некоторым людям (рабам) ставили клеймо на теле с именем и принадлежностью. Конечно такой подход не удобен.

Ниже представлен пример реализации интерфейсов IContainer, IComponent и ISite, но также можно воспользоваться уже имеющимися классами контейнера Container и компонентов Component, которые идут в поставке FCL (Framework Class Library).

См. пример к главе: \016_Iterator\002_Containers

Коллекции в программировании классифицируются по общим характеристикам, по логике организации и по реализации.

Коллекции классифицирующиеся по общим характеристикам могут иметь фиксированный или динамически изменяющийся размер, а также содержать элементы одного типа или разных типов. Коллекция содержащая разнотипные элементы называется – гетерогенной коллекцией (например, ArrayList).

Коллекции классифицирующиеся по логике организации: вектор, матрица, многомерный массив, зубчатый (рваный) массив, список, стек, очередь, дека, ассоциативный массив «словарь», множество, мультимножество, кортеж, сеть и др.

Коллекции классифицирующиеся по уровню реализации: массив, односвязный список, двусвязный список, стек, хеш-таблица, битовый массив, битовый вектор и др.

Для реализации простейшей (в общем виде) коллекции в .NET Framework используется интерфейс ICollection. Ниже показан пример реализации интерфейса ICollection. Как видно из примера интерфейс коллекции ICollection расширяет собой интерфейс простейшей перечисляемой сущности IEnumerable.

См. пример к главе: \016_Iterator\006_IList

Предлагается рассмотреть создание класса итератора при помощи оператора yield, как оператора автоматической генерации программного кода класса итератора. Задача оператора yield сгенерировать (написать без участия человека) программный код класса итератора для той коллекции в которой он используется. Оператор yield облегчает работу программиста, избавляя его от необходимости вручную писать код класса итератора.  При этом сама реализация класса итератора оказывается скрытой от программиста, что добавляет немного «магии», для тех разработчиков, которые не до конца понимают работу оператора yield.

Пример ниже показывает создание итератора с использованием оператора yield для коллекции Enumerable размерностью в один строковой элемент со значением "Element".

См. пример к главе: \016_Iterator\007_Yield

Для того чтобы понять работу оператора yield, желательно посмотреть на код класса итератора, который генерируется оператором yield. Для этого рекомендуется воспользоваться инструментом для обратной инженерии (дизассемблирования) – программой  dotPeek от компании  JetBrains известной как производителя программы  ReSharper.

Для анализа потребуется открыть в программе dotPeek исполняемый файл *.exe содержащий код итератора. В результате дизассемблирования будет представлен следующий код (для упрощения понимания дизассемблированный код был упрощен):

См. пример к главе: \016_Iterator\008_Yield

При сравнении двух участков кода видно, сколько рутинной работы по созданию класса итератора Enumerator и реализации логики по взаимодействию с коллекцией Enumerable, взял на себя оператор yield. Программисту остается сосредоточится на бизнес логике приложения, а не задумываться над деталями технической реализации класса итератора.

Структура паттерна на языке UML

Классическое представление

См. пример к главе: \016_Iterator\001_Iterator

Представление Microsoft .NET

См. пример к главе: \016_Iterator\003_Enumerator

Структура паттерна на языке C#

Классическое представление

См. пример к главе: \016_Iterator\001_Iterator

Представление Microsoft .NET

См. пример к главе: \016_Iterator\003_Enumerator

Участники

  • Iterator (IEnumerator) - Итератор:

 Предоставляет интерфейс (набор методов) для доступа к коллекции и обхода элементов.

  • Concretelterator - Конкретный итератор:

Реализует интерфейс класса Iterator. Следит за позицией текущего элемента при переборе коллекции (Aggregate).

  • Aggregate (IEnumerable) - Агрегат:

Предоставляет интерфейс коллекции (набор методов) в том числе методы для создания объекта-итератора.

  • ConcreteAggregate - Конкретный агрегат:

Реализует интерфейс коллекции и хранит в себе элементы.

Отношения между участниками

Отношения между классами

  • Конкретный класс ConcreteAggregate связан связью отношения наследования с абстрактным классом Aggregate и связью отношения зависимости с конкретным классом Concretelterator.
  • Конкретный класс Concretelterator связан связью отношения наследования с абстрактным классом Iterator и связью отношения ассоциации с конкретным классом ConcreteAggregate.

Отношения между объектами

  • Concretelterator отслеживает текущий элемент в коллекции (Aggregate) и может вычислить следующий за ним элемент.

Мотивация

Правильно спроектированная коллекция (составной объект), должна предоставлять возможность доступа к своим элементам удобным способом, при этом не раскрывать своего внутреннего устройства (представления). Иногда требуется организовать обход элементов коллекции разными способами. При этом не желательно формировать интерфейс класса List из операций различных вариантов обхода списка (коллекции), даже если все операции можно предусмотреть заранее. Кроме того, иногда требуется, чтобы одновременно имелось несколько вариантов обхода списка.

Применимость паттерна

Паттерн Iterator рекомендуется использовать, когда:

  • Требуется организовать доступ к элементам (содержимому) коллекции (агрегированного объекта) без раскрытия устройства (внутреннего представления) коллекции.
  • Необходимо организовать несколько вариантов обхода коллекции.
  • Требуется сформировать единообразный интерфейс (набор методов) с целью унифицированного обращения к элементам из различных коллекций, другими словами для поддержки техники «полиморфной итерации» (один итератор для нескольких коллекций).

Результаты

Использование паттерна Iterator предоставляет следующие возможности:

  • Поддержка различных видов обхода коллекции (агрегата).

Сложные агрегаты (составные объекты – коллекции или контейнеры) можно обходить по-разному. Например, если внутренне устройство коллекции (агрегата) имеет древообразную структуру, требуется организовать правильный обход частей дерева или генерацию новых частей дерева. Итератор может обходить дерево во внутреннем порядке (только части дерева) или в прямом порядке (от корня к листьям). Использование итераторов позволяет организовать динамическую подмену алгоритмов обхода дерева, для этого достаточно заменить один экземпляр итератора другим. Для поддержки новых алгоритмов обхода можно создать новую разновидность итератора.

  • Итераторы упрощают интерфейс коллекции (агрегата).

Наличие интерфейса (набора методов) для обхода элементов коллекции в классе итератор (Iterator), избавляет от дублирования этого интерфейса непосредственно в классе агрегата (Aggregate), тем самым упрощая интерфейс агрегата.

  • Для одной коллекции (агрегата), одновременно может быть активно несколько вариантов обхода.

Имеется возможность организации одновременного обращения к элементам коллекции, параллельно из разных потоков. Такие коллекции называются «потокобезопасными коллекциями» (Concurrency collections), а иногда их называют просто «параллельными коллекциями». Потокобезопасные коллекции следует использовать вместо обычных коллекций, только тогда, когда требуется организовать одновременный доступ к элементам коллекции из разных потоков.  При работе с такими коллекциями, итератор следит за состоянием обхода, отслеживая текущую позицию в коллекции и синхронизируя свою работу с работой коллекции и работой других итераторов.

В поставку Microsoft .NET FCL включены готовые решения потокобезопасных коллекций. Пространство имен System.Collections.Concurrent содержит классы потокобезопасных коллекций, такие как: BlockingCollection<T>, ConcurrentBag<T>, ConcurrentDictionary<TKey, TValue>, ConcurrentQueue<T>, ConcurrentStack<T>, OrderablePartitioner<TSource>, Partitioner и Partitioner<TSource>. При построении собственной потокобезопасной коллекции рекомендуется реализовать интерфейс IProducerConsumerCollection<T>, который задает методы для работы с потокобезопасными коллекциями.

Реализация

Полезные приемы реализации паттерна Iterator:

Существует несколько способов реализации паттерна итератор. Решение о том, какой способ выбрать, часто зависит от управляющих структур, поддерживаемых языком программирования. Некоторые языки (например, C#) поддерживают реализацию паттерна Iterator в своем синтаксисе (оператор - yield).

Наиболее употребительные варианты реализации паттерна итератор:

  • Внешний и внутренний итераторы.

Важнейший вопрос состоит в том, что управляет итерацией - сам итератор или клиент, который пользуется итератором. Если итерацией управляет клиент, то итератор называется внешним (активным), в противном случае если итерация производится автоматически, без участия клиента, итератор называется внутренним (пассивным). Термины «активный» и «пассивный» относятся к роли клиента, а не к действиям, выполняемым итератором. Клиенты, применяющие внешний (активный) итератор, должны явно запрашивать у итератора следующий элемент, чтобы двигаться дальше по коллекции. Напротив, в случае использования внутреннего (пассивного) итератора клиент передает итератору некоторую операцию (или лямбда оператор), а итератор уже сам применяет эту операцию к каждому посещенному во время обхода элементу коллекции. Сильные стороны внутренних (пассивных) итераторов наиболее отчетливо проявляются в таких языках, как C#, где есть анонимные функции, замыкание (closure) и продолжения (continuation). Также, внутренние (пассивные) итераторы проще в использовании, поскольку они легко конфигурируются новой логикой по работе с коллекцией.

Пример работы внутреннего итератора реализованного с использованием оператора yield:

См. пример к главе: \016_Iterator\004_InteralIterator [002]

  • Итератор – курсор.

Алгоритм обхода коллекции может содержаться не только в итераторе. Алгоритм обхода может находиться непосредственно в коллекции, а коллекция может использовать итератор только для хранения состояния итерации (например, указателя позиции текущего элемента). Такого рода итератор называют курсором, поскольку он всего лишь указывает текущую позицию элемента в коллекции. Клиент может вызывать метод Next коллекции, передавая методу Next в качестве аргумента итератор-курсор. Метод Next в свою очередь состояние итератора-курсора (например, увеличивает значение поля position на один). Если же за алгоритм обхода коллекции отвечает итератор, то для одной и той же коллекции можно использовать разные алгоритмы обхода, а также проще применить один алгоритм обхода к разным коллекциям (полиморфная итерация). Часто бывает так, что для выполнения алгоритма обхода может понадобиться предоставить доступ к закрытым членам коллекции. В таком случае потребуется организовать то перенос алгоритма обхода в итератор, что нарушает инкапсуляцию коллекции. Для сохранения инкапсуляции коллекции можно итератор сделать «nested» классом, вложенным в класс коллекции, что позволит сохранить инкапсуляцию коллекции.

  • Устойчивый (робастный) итератор.

Изменение коллекции в то время, как совершается ее обход, может оказаться опасным. Если во время обхода коллекции добавляются или удаляются элементы, то не исключено, что некоторый элемент может быть «посещен» дважды или вообще ни разу. Можно скопировать коллекцию и обходить ее копию, но такой подход не является эффективным и обходится слишком дорого. Устойчивый итератор (robust) гарантирует, что ни вставки новых элементов, ни удаление существующих не помешают правильному обходу коллекции, причем стабильность работы коллекции достигается без создания ее копии. Имеются различные способы реализации устойчивых итераторов. В большинстве случаев устойчивый итератор регистрируется в коллекции. При вставке или удалении элемента, коллекция либо подправляет внутреннее состояние всех созданных ею итераторов, либо организует порядок следования своих элементов так, чтобы обход элементов выполнялся правильно.

  • Полиморфные итераторы.

Полиморфный итератор, это такой итератор который может использоваться для обхода элементов разных коллекций (один итератор для нескольких коллекций). При реализации полиморфных итераторов могут возникать определенные сложности. Может понадобиться, чтобы объект-итератор создавался в динамической памяти (на куче) фабричным методом. Поэтому использовать их стоит только тогда, когда есть необходимость в полиморфизме. В противном случае применяйте конкретные итераторы, которые вполне можно распределять в стеке.

  • Итераторы для коллекций с древообразной структурой.

Реализация внешних (активных) итераторов для рекурсивно агрегированных древообразных структур данных (деревьев) может оказаться затруднительной, так как положение элемента в древообразной структуре данных (дереве) может находиться на нескольких уровнях вложенности (лист, одновременно принадлежащий нескольким ветвям). Поэтому, чтобы отследить позицию текущего элемента, внешний (активный) итератор должен хранить все возможные пути к определенному элементу и исключить повторную обработку такого элемента при обходе дерева. Иногда проще воспользоваться внутренним (пассивным) итератором.

Деревья часто приходится обходить несколькими способами. Самые распространенные способы - это обход в прямом порядке (от корня к листьям), обратном порядке (от листьев к корню – когда у потомков имеются ссылки на родителей), внутреннем порядке (обход отдельной ветки – части дерева), а также обход в ширину (обход элементов на всех ветках дерева располагающихся на определенном уровне вложенности. Например, если данную книгу представить в виде дерева, то при обходе в ширину читатель будет переходить к каждой главе и читать только раздел «Мотивация»). Каждый вид обхода дерева можно поддержать реализацией отдельного итератора.

  • Пустой итератор.

Пустой итератор NullIterator - это вырожденная форма итератора. Пустой итератор NullIterator всегда считает, что обход завершен, то есть его операция IsDone всегда возвращает истину (true). Применение пустого итератора может упростить обход древовидных структур. В каждой точке обхода мы запрашиваем у текущего элемента итератор для его потомков. Ветви дерева обычно, возвращают полноценный итератор, но листовые элементы могут возвращать пустой итератор NullIterator, что позволяет реализовать обход элементов дерева единообразно.

В языке C# пустой итератор реализуется при помощи связки операторов yield break. Предлагается рассмотреть создание пустого итератора на примере.

Для того чтобы понять работу конструкции yield break, желательно посмотреть на код класса итератора, который генерируется конструкцией yield break. Для этого рекомендуется воспользоваться инструментом для обратной инженерии (дизассемблирования) – программой  dotPeek от компании  JetBrains известной как производителя программы  ReSharper.

Для анализа потребуется открыть в программе dotPeek исполняемый файл *.exe содержащий код итератора. В результате дизассемблирования будет представлен следующий код (для упрощения понимания дизассемблированный код был упрощен):

Пустой итератор Enumerator всегда считает, что обход завершен, то есть его операция MoveNext всегда возвращает ложь (false).

Известные применения паттерна в .Net

Важно отметить, что классическое представление паттерна Iterator отличается от представления, рекомендуемого Microsoft. Но суть использования техники «перечисляемый-перечислитель» которая описывается паттерном Iterator при этом не меняется.

Паттерн Iterator, выражен в платформе .NET в виде техники создания составных объектов, основанных на технике «перечисляемый-перечислитель» коллекций, через реализацию интерфейсов IEnumerable и IEnumerator. Оператор автоматической генерации программного кода коллекций - yield, также выражает идею использования паттерна Iterator.

Практически во всех коллекциях, идущих в поставке Microsoft .NET Framework присутствуют реализации итераторов. Следует внимательно подходить к выбору класса коллекции, так как использование неправильного типа коллекции может привести к неоптимальной работе программы. В общем случае не следует использовать типы в пространстве имен System.Collections, если явным образом не используется версия 1.1 платформы .NET Framework. Во всех других случаях рекомендуется использовать универсальные (generic) и параллельные коллекции, так как они оптимизированы, обладают большей типобезопасностью и содержат дополнительные функциональные возможности.

Перед использованием той или иной системной коллекции, необходимо ответить на следующие вопросы:

  • Вопрос: Требуется ли использовать такой список, элементы которого сразу удаляются после получения их значения?  

 

  • Если да, то имеет смысл рассмотреть возможность использования универсального класса Queue<T>, если требуется работа по принципу (FIFO) или универсального класса Stack<T>, если требуется работа по принципу (LIFO). Для безопасного доступа из нескольких потоков следует использовать параллельные версии очереди или стека - классы ConcurrentQueue<T> или ConcurrentStack<T> соответственно.

 

  • Вопрос: Требуется ли получать доступ к элементам коллекции в определенном порядке (FIFO, LIFO) или в произвольным порядке? 

 

  • Если да, то рекомендуется использовать класс Queue, универсальный класс Queue<T> или универсальный класс ConcurrentQueue<T>, которые предоставляют доступ по принципу FIFO. 

Класс Stack, универсальный класс Stack<T> или универсальный класс ConcurrentStack<T> предоставляют доступ по принципу LIFO. 

Универсальный класс LinkedList<T> предоставляет последовательный доступ от начала списка к концу списка или наоборот. 

 

  • Вопрос: Необходимо ли получать доступ к элементам коллекции по индексу?  

 

  • Если да, то рекомендуется использовать классы ArrayList и StringCollection, и универсальный класс List<T>, которые предоставляют доступ к своим элементам по индексу, начиная отсчет с нулевого индекса.  

Классы Hashtable, SortedList, ListDictionary и StringDictionary, а также универсальные классы Dictionary<TKey, TValue> и SortedDictionary<TKey, TValue> предоставляют доступ к своим элементам по ключу. 

Классы NameObjectCollectionBase и NameValueCollection, а также универсальные классы KeyedCollection<TKey, TItem> и SortedList<TKey, TValue> предоставляют доступ к своим элементам по индексу с отсчетом от нуля или по ключу. 

 

  • Вопрос: Будет ли каждый элемент содержать только одно значение, сочетание из одного ключа и одного значения или сочетание из одного ключа и нескольких значений?  

 

  • Одно значение. Можно использовать любую из коллекций, основанных на интерфейсе IList или на универсальном интерфейсе IList<T>

Один ключ и одно значение. Можно использовать любую из коллекций, основанных на интерфейсе IDictionary или на универсальном интерфейсе IDictionary<TKey, TValue>

Одно значение с внедренным ключом. Можно использовать универсальный класс KeyedCollection<TKey, TItem>

Один ключ и несколько значений. Можно использовать класс NameValueCollection

 

  • Вопрос: Требуется ли сортировка элементов в порядке, отличном от того порядка в котором элементы поступают (добавляются) в коллекцию?  

 

  • Если да, то рекомендуется использовать класс Hashtable, который сортирует свои элементы по их хэш-коду. 

Класс SortedList и универсальные классы SortedDictionary<TKey, TValue> и SortedList<TKey, TValue> сортируют свои элементы по их ключам на основе реализации интерфейса IComparer и универсального интерфейса IComparer<T>

Класс ArrayList предоставляет метод Sort, который принимает реализацию IComparer в качестве параметра.  Его универсальный аналог — универсальный класс List<T> предоставляет метод Sort, который принимает реализацию универсального интерфейса IComparer<T> в качестве параметра.

 

  • Вопрос: Необходимо ли организовать быстрый поиск по коллекции, а также быстрое помещение новых элементов в коллекцию и быстрое извлечение существующих элементов из коллекции?  

 

  • Если да, то рекомендуется использовать класс ListDictionary, так как он работает быстрее, чем Hashtable для небольших коллекций (10 элементов или меньше).  Универсальный класс Dictionary<TKey, TValue> предоставляет более быстрый просмотр, чем универсальный класс SortedDictionary<TKey, TValue>. Многопоточной реализацией является класс ConcurrentDictionary<TKey, TValue>. Класс ConcurrentBag<T> предоставляет быструю многопоточную вставку для неупорядоченных данных.

 

  • Вопрос: Требуется ли использовать коллекцию только для хранения строк?  

 

  • Если да, то рекомендуется использовать классы StringCollection (основанный на IList) и StringDictionary (основанный на IDictionary) которые находятся в пространстве имен System.Collections.Specialized.  

Кроме того, можно использовать любой из универсальных классов коллекций из пространства имен System.Collections.Generic как строго типизированную строковую коллекцию, указав класс String в качестве параметра типа.

Известные применения паттерна в .Net

Важно отметить, что классическое представление паттерна Iterator отличается от представления, рекомендуемого Microsoft. Но суть использования техники «перечисляемый-перечислитель» которая описывается паттерном Iterator при этом не меняется.

Паттерн Iterator, выражен в платформе .NET в виде техники создания составных объектов, основанных на технике «перечисляемый-перечислитель» коллекций, через реализацию интерфейсов IEnumerable и IEnumerator. Оператор автоматической генерации программного кода коллекций - yield, также выражает идею использования паттерна Iterator.

Практически во всех коллекциях, идущих в поставке Microsoft .NET Framework присутствуют реализации итераторов. Следует внимательно подходить к выбору класса коллекции, так как использование неправильного типа коллекции может привести к неоптимальной работе программы. В общем случае не следует использовать типы в пространстве имен System.Collections, если явным образом не используется версия 1.1 платформы .NET Framework. Во всех других случаях рекомендуется использовать универсальные (generic) и параллельные коллекции, так как они оптимизированы, обладают большей типобезопасностью и содержат дополнительные функциональные возможности.

Перед использованием той или иной системной коллекции, необходимо ответить на следующие вопросы:

  • Вопрос: Требуется ли использовать такой список, элементы которого сразу удаляются после получения их значения?  
  • Если да, то имеет смысл рассмотреть возможность использования универсального класса Queue<T>, если требуется работа по принципу (FIFO) или универсального класса Stack<T>, если требуется работа по принципу (LIFO). Для безопасного доступа из нескольких потоков следует использовать параллельные версии очереди или стека - классы ConcurrentQueue<T> или ConcurrentStack<T> соответственно.

 

  • Вопрос: Требуется ли получать доступ к элементам коллекции в определенном порядке (FIFO, LIFO) или в произвольным порядке? 
  • Если да, то рекомендуется использовать класс Queue, универсальный класс Queue<T> или универсальный класс ConcurrentQueue<T>, которые предоставляют доступ по принципу FIFO. 

Класс Stack, универсальный класс Stack<T> или универсальный класс ConcurrentStack<T> предоставляют доступ по принципу LIFO. 

Универсальный класс LinkedList<T> предоставляет последовательный доступ от начала списка к концу списка или наоборот. 

 

  • Вопрос: Необходимо ли получать доступ к элементам коллекции по индексу?  
  • Если да, то рекомендуется использовать классы ArrayList и StringCollection, и универсальный класс List<T>, которые предоставляют доступ к своим элементам по индексу, начиная отсчет с нулевого индекса.  

Классы Hashtable, SortedList, ListDictionary и StringDictionary, а также универсальные классы Dictionary<TKey, TValue> и SortedDictionary<TKey, TValue> предоставляют доступ к своим элементам по ключу. 

Классы NameObjectCollectionBase и NameValueCollection, а также универсальные классы KeyedCollection<TKey, TItem> и SortedList<TKey, TValue> предоставляют доступ к своим элементам по индексу с отсчетом от нуля или по ключу. 

 

  • Вопрос: Будет ли каждый элемент содержать только одно значение, сочетание из одного ключа и одного значения или сочетание из одного ключа и нескольких значений?  
  • Одно значение. Можно использовать любую из коллекций, основанных на интерфейсе IList или на универсальном интерфейсе IList<T>

Один ключ и одно значение. Можно использовать любую из коллекций, основанных на интерфейсе IDictionary или на универсальном интерфейсе IDictionary<TKey, TValue>

Одно значение с внедренным ключом. Можно использовать универсальный класс KeyedCollection<TKey, TItem>

Один ключ и несколько значений. Можно использовать класс NameValueCollection.

 

  • Вопрос: Требуется ли сортировка элементов в порядке, отличном от того порядка в котором элементы поступают (добавляются) в коллекцию?  
  • Если да, то рекомендуется использовать класс Hashtable, который сортирует свои элементы по их хэш-коду. 

Класс SortedList и универсальные классы SortedDictionary<TKey, TValue> и SortedList<TKey, TValue> сортируют свои элементы по их ключам на основе реализации интерфейса IComparer и универсального интерфейса IComparer<T>

Класс ArrayList предоставляет метод Sort, который принимает реализацию IComparer в качестве параметра.  Его универсальный аналог — универсальный класс List<T> предоставляет метод Sort, который принимает реализацию универсального интерфейса IComparer<T> в качестве параметра.

 

  • Вопрос: Необходимо ли организовать быстрый поиск по коллекции, а также быстрое помещение новых элементов в коллекцию и быстрое извлечение существующих элементов из коллекции?  
  • Если да, то рекомендуется использовать класс ListDictionary, так как он работает быстрее, чем Hashtable для небольших коллекций (10 элементов или меньше).  Универсальный класс Dictionary<TKey, TValue> предоставляет более быстрый просмотр, чем универсальный класс SortedDictionary<TKey, TValue>. Многопоточной реализацией является класс ConcurrentDictionary<TKey, TValue>. Класс ConcurrentBag<T> предоставляет быструю многопоточную вставку для неупорядоченных данных.

 

  • Вопрос: Требуется ли использовать коллекцию только для хранения строк?  
  • Если да, то рекомендуется использовать классы StringCollection (основанный на IList) и StringDictionary (основанный на IDictionary) которые находятся в пространстве имен System.Collections.Specialized.  

Кроме того, можно использовать любой из универсальных классов коллекций из пространства имен System.Collections.Generic как строго типизированную строковую коллекцию, указав класс String в качестве параметра типа.

© 2017 ITVDN, все права защищены