Поделиться через


Рекомендации по использованию коллекций

Примечание.

Это содержимое перепечатывается разрешением Pearson Education, Inc. из руководства по проектированию платформы: соглашения, идиомы и шаблоны для повторно используемых библиотек .NET, 2-го выпуска. Этот выпуск был опубликован в 2008 году, и книга с тех пор была полностью пересмотрена в третьем выпуске. Некоторые сведения на этой странице могут быть устаревшими.

Любой тип, предназначенный специально для управления группой объектов с некоторыми общими характеристиками, может рассматриваться как коллекция. Для таких типов почти всегда уместно реализовать IEnumerable или IEnumerable<T>. Поэтому в этом разделе мы рассматриваем только типы, реализующие один или оба этих интерфейса, как коллекции.

❌ НЕ используйте слабо типизированные коллекции в общедоступных API.

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

❌ НЕ используйте ArrayList или List<T> в общедоступных API-интерфейсах.

Эти типы представляют собой структуры данных, предназначенные для использования во внутренней реализации, а не в общедоступных API-интерфейсах. Элемент List<T> оптимизирован для повышения производительности и энергопотребления за счет чистоты и гибкости интерфейсов API. Например, если вы возвращаете List<T>, то никогда не сможете получать уведомления, когда клиентский код изменяет коллекцию. Кроме того, List<T> предоставляет множество элементов, таких как BinarySearch, которые не являются полезными или применимыми во многих сценариях. В следующих двух разделах описаны типы (абстракции), специально предназначенные для использования в общедоступных API-интерфейсах.

❌ НЕ используйте Hashtable или Dictionary<TKey,TValue> в общедоступных API-интерфейсах.

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

❌ НЕ используйте IEnumerator<T>, IEnumerator или любой другой тип, реализующий любой из этих интерфейсов, за исключением типа возвращаемого значения метода GetEnumerator.

Типы, возвращающие перечислители из методов, отличных от GetEnumerator, нельзя использовать с оператором foreach.

❌ НЕ реализуйте IEnumerator<T> и IEnumerable<T> для одного и того же типа. То же относится и к неуниверсальным интерфейсам IEnumerator и IEnumerable.

Параметры коллекции

✔️ ИСПОЛЬЗУЙТЕ в качестве типа параметра наименее специализированный тип. Большинство элементов, принимающих коллекции в качестве параметров, используют интерфейс IEnumerable<T>.

❌ Не используйте ICollection<T> и ICollection в качестве параметра только для доступа к свойству Count.

Вместо этого рекомендуется использовать IEnumerable<T> и IEnumerable, а также динамически проверять, реализует ли объект ICollection<T> или ICollection.

Свойства коллекции и возвращаемые значения

❌ НЕ предоставляйте настраиваемые свойства коллекции.

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

✔️ ИСПОЛЬЗУЙТЕ Collection<T> или подкласс Collection<T> для свойств или возвращаемых значений, представляющих коллекции для чтения или записи.

Если Collection<T> не соответствует какому-либо требованию (например, коллекция не должна реализовывать IList), используйте пользовательскую коллекцию, реализуя IEnumerable<T>, ICollection<T> или IList<T>.

✔️ ИСПОЛЬЗУЙТЕ ReadOnlyCollection<T>, подкласс ReadOnlyCollection<T> или в редких случаях IEnumerable<T> для свойств или возвращаемых значений, представляющих коллекции только для чтения.

Как правило, предпочтительнее использовать ReadOnlyCollection<T>. Если это не соответствует какому-либо требованию (например, коллекция не должна реализовывать IList), используйте пользовательскую коллекцию, реализуя IEnumerable<T>, ICollection<T> или IList<T>. Если вы реализуете пользовательскую коллекцию, доступную только для чтения, реализуйте ICollection<T>.IsReadOnly, чтобы вернуть true.

Если вы уверены, что единственным сценарием, который вы хотите поддерживать, является однопроходная итерация, можно просто использовать IEnumerable<T>.

✔️ РЕКОМЕНДУЕТСЯ использовать подклассы универсальных базовых коллекций вместо непосредственного использования коллекций.

Это позволяет улучшить именование и добавить вспомогательные элементы, отсутствующие в базовых типах коллекций. Это хорошо подходит для интерфейсов API высокого уровня.

✔️ РЕКОМЕНДУЕТСЯ возвращать подкласс Collection<T> или ReadOnlyCollection<T> из очень часто используемых методов и свойств.

Это позволит добавлять вспомогательные методы или изменять реализацию коллекции в будущем.

✔️ РЕКОМЕНДУЕТСЯ использовать коллекцию с ключом, если элементы, хранящиеся в коллекции, имеют уникальные ключи (имена, идентификаторы и т. д.). Коллекции с ключом — это коллекции, которые могут индексироваться как целое число и как ключ и обычно реализуются путем наследования от KeyedCollection<TKey,TItem>.

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

❌ НЕ возвращайте значения NULL из свойств коллекции или из методов, возвращающих коллекции. Вместо этого следует возвращать пустую коллекцию или пустой массив.

Общее правило состоит в том, что нулевые и пустые (без элементов) коллекции или массивы должны обрабатываться одинаково.

Моментальные снимки и динамические коллекции

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

❌ НЕ возвращайте коллекции моментальных снимков из свойств. Свойства должны возвращать динамические коллекции.

Методы получения свойств должны быть очень простыми операциями. Для возврата моментального снимка необходимо создать копию внутренней коллекции в операции O(n).

✔️ ИСПОЛЬЗУЙТЕ либо коллекцию моментальных снимков, либо динамический элемент IEnumerable<T> (или его подтип) для представления коллекций, которые являются временными (т. е. могут изменяться без явного изменения коллекции).

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

Выбор между массивами и коллекциями

✔️ ПРЕДПОЧИТАЙТЕ коллекции массивам.

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

Однако при разработке низкоуровневых интерфейсов API лучше использовать массивы для сценариев чтения и записи. Массивы имеют меньший объем памяти, что позволяет сократить рабочий набор, а доступ к элементам массива выполняется быстрее, так как он оптимизирован средой выполнения.

✔️ РАССМОТРИТЕ возможность использования массивов в низкоуровневых API-интерфейсах для минимизации потребления памяти и увеличения производительности.

✔️ ИСПОЛЬЗУЙТЕ массивы байтов вместо коллекций байтов.

❌ НЕ используйте массивы для свойств, если свойство должно возвращать новый массив (например, копию внутреннего массива) каждый раз при вызове метода получения свойства.

Реализация пользовательских коллекций

✔️ РЕАЛИЗУЙТЕ наследование от Collection<T>, ReadOnlyCollection<T> или KeyedCollection<TKey,TItem> при проектировании новых коллекций.

✔️ РЕАЛИЗУЙТЕ IEnumerable<T> при проектировании новых коллекций. Рассмотрите возможность реализации ICollection<T> или даже IList<T> там, где это уместно.

При реализации такой пользовательской коллекции как можно более точно следуйте шаблону API, установленному Collection<T> и ReadOnlyCollection<T>. То есть реализуйте одни и те же элементы явно, назовите параметры так, как они называются в эти двух коллекциях, и так далее.

✔️ РАССМОТРИТЕ возможность реализации неуниверсальных интерфейсов коллекции (IList и ICollection), если коллекция часто будет передаваться API, принимающим эти интерфейсы в качестве входных данных.

❌ ИЗБЕГАЙТЕ реализации интерфейсов коллекций для типов со сложными API, не связанными с концепцией коллекции.

❌ ИЗБЕГАЙТЕ наследования от неуниверсальных базовых коллекций, таких как CollectionBase. Используйте вместо них типы данных Collection<T>, ReadOnlyCollection<T> и KeyedCollection<TKey,TItem>.

Именование пользовательских коллекций

Коллекции (реализующие IEnumerableтипы) создаются главным образом по двум причинам: (1) для создания новой структуры данных с определенными структурами и часто разными характеристиками производительности, чем существующие структуры данных (например, List<T>, LinkedList<T>, Stack<T>и (2) для создания специализированной коллекции для хранения определенного набора элементов (например, StringCollection). Структуры данных чаще всего используются во внутренней реализации приложений и библиотек. Специализированные коллекции, главным образом, должны предоставляться в API-интерфейсах (как типы свойств и параметров).

✔️ ИСПОЛЬЗУЙТЕ суффикс Dictionary в именах абстракций, реализующих IDictionary или IDictionary<TKey,TValue>.

✔️ ИСПОЛЬЗУЙТЕ суффикс Collection в именах типов, реализующих IEnumerable (или любой из потомков) и представляющих список элементов.

✔️ ИСПОЛЬЗУЙТЕ соответствующее имя структуры данных для пользовательских структур данных.

❌ Не используйте суффиксы, которые подразумевают определенную реализацию, например LinkedList или Hashtable, в именах абстракций коллекции.

✔️ РАССМОТРИТЕ возможность добавления к именам коллекций префикса с именем типа элемента. Например, коллекция, в которой хранятся элементы типа Address (реализация IEnumerable<Address>), должна иметь имя AddressCollection. Если тип элемента является интерфейсом, то префикс I типа элемента можно опустить. Таким образом, набор элементов IDisposable можно назвать DisposableCollection.

✔️ РАССМОТРИТЕ возможность использования префикса ReadOnly в именах коллекций только для чтения, если соответствующая записываемая коллекция может быть добавлена или уже существует на платформе.

Например, доступная только для чтения коллекция строк должна называться ReadOnlyStringCollection.

Фрагменты: © Корпорация Майкрософт (Microsoft Corporation), 2005, 2009. Все права защищены.

Перепечатано с разрешения Pearson Education, Inc. из книги Инфраструктура программных проектов. Соглашения, идиомы и шаблоны для многократно используемых библиотек .NET (2-е издание), авторы: Кржиштоф Цвалина (Krzysztof Cwalina) и Брэд Абрамс (Brad Abrams). Книга опубликована 22 октября 2008 г. издательством Addison-Wesley Professional в рамках серии, посвященной разработке для Microsoft Windows.

См. также