Бөлісу құралы:


Рекомендации по коллекциям

Замечание

Это содержимое перепечатывается разрешением 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 для коллекции.

✔️ DO использует 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 из свойств коллекции или из методов, возвращающих коллекции. Верните пустую коллекцию или пустой массив.

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

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

Коллекции, представляющие состояние в определенный момент времени, называются коллекциями моментальных снимков. Например, коллекция, содержащая строки, возвращаемые из запроса базы данных, будет снимком. Коллекции, которые всегда представляют текущее состояние, называются динамическими коллекциями. Например, коллекция 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 (как типы свойств и параметров).

✔️ Используйте суффикс "Словарь" в именах абстракций, реализующих IDictionary или IDictionary<TKey,TValue>.

✔️ Используйте суффикс "Коллекция" в именах типов, реализующих IEnumerable (или любого из его потомков) и представляющих список элементов.

✔️ Используйте соответствующее имя структуры данных для пользовательских структур данных.

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

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

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

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

© Часть 2005, 2009 Корпорация Майкрософт. Все права защищены.

Перепечатан с разрешения Pearson Education, Inc. из Руководство по проектированию: Соглашения, идиомы и шаблоны для повторного использования библиотек .NET, 2-е издание Кшиштоф Чвалина и Брэд Абрамс, опубликованное 22 октября 2008 года Addison-Wesley Профессиональный в рамках серии разработки Microsoft Windows.

См. также