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