Partager via


Instructions relatives aux collections

Remarque

Ce contenu est réimprimé avec l’autorisation de Pearson Education, Inc. à partir des Instructions de conception d’une infrastructure : conventions, idiomes et modèles des bibliothèques réutilisables .NET, 2ème édition. Cette édition a été publiée en 2008, et le livre a été entièrement révisé dans la troisième édition. Certaines informations de cette page peuvent être obsolètes.

Tout type conçu spécifiquement pour manipuler un groupe d’objets ayant des caractéristiques communes peut être considéré comme une collection. Il est presque toujours approprié pour ces types d’implémenter IEnumerable ou IEnumerable<T>. Ainsi, dans cette section, nous considérons uniquement les types implémentant une des interfaces ou les deux comme des collections.

❌ N’utilisez PAS de collections faiblement typées dans les API publiques.

Le type de toutes les valeurs et de tous les paramètres de retour représentant les éléments de collection doit être le type d’élément exact, et non un de ses types de base (cela s’applique uniquement aux membres publics de la collection).

❌ N’utilisez PAS ArrayList ou List<T> dans les API publiques.

Ces types sont des structures de données conçues pour être utilisées dans l’implémentation interne, et non dans les API publiques. List<T> est optimisé pour les performances et la puissance au détriment de la propreté et de la flexibilité des API. Par exemple, si vous retournez List<T>, vous ne pourrez jamais recevoir de notifications lorsque le code client modifie la collection. En outre, List<T> expose de nombreux membres, comme BinarySearch, qui ne sont pas utiles ou applicables dans de nombreux scénarios. Les deux sections suivantes décrivent les types (abstractions) conçus spécifiquement pour une utilisation dans les API publiques.

❌ N’utilisez PAS Hashtable ou Dictionary<TKey,TValue> dans les API publiques.

Ces types sont des structures de données conçues pour être utilisées dans l’implémentation interne. Les API publiques doivent utiliser IDictionary, IDictionary <TKey, TValue> ou un type personnalisé implémentant une des interfaces ou les deux.

❌ N’utilisez PAS IEnumerator<T>, IEnumerator ou tout autre type qui implémente l’une de ces interfaces, sauf en tant que type de retour d’une méthode GetEnumerator.

Les types qui retournent des énumérateurs à partir de méthodes autres que GetEnumerator ne peuvent pas être utilisés avec l’instruction foreach.

❌ N’implémentez PAS à la fois IEnumerator<T> et IEnumerable<T> sur le même type. Il en va de même pour les interfaces non génériques IEnumerator et IEnumerable.

Paramètres de collection

✔️ UTILISEZ le type le moins spécialisé possible comme type de paramètre. La plupart des membres prenant des collections en tant que paramètres utilisent l’interface IEnumerable<T>.

❌ ÉVITEZ d’utiliser ICollection<T> ou ICollection comme paramètre uniquement pour accéder à la propriété Count.

Au lieu de cela, envisagez d’utiliser IEnumerable<T> ou IEnumerable, et vérifiez dynamiquement si l’objet implémente ICollection<T> ou ICollection.

Propriétés de la collection et valeurs de retour

❌ NE fournissez PAS de propriétés de collection pouvant être définies.

Les utilisateurs pourraient remplacer le contenu de la collection en effaçant d’abord la collection, puis en ajoutant du nouveau contenu. Si le remplacement de la collection entière est un scénario courant, envisagez de fournir la méthode AddRange sur la collection.

✔️ UTILISEZ Collection<T> ou une sous-classe de Collection<T> pour les propriétés ou les valeurs de retour représentant des collections en lecture/écriture.

Si Collection<T> ne répond pas à certaines exigences (par exemple, la collection ne doit pas implémenter IList), utilisez une collection personnalisée en implémentant IEnumerable<T>, ICollection<T> ou IList<T>.

✔️ Utilisez ReadOnlyCollection<T>, une sous-classe de ReadOnlyCollection<T>, ou dans de rares cas IEnumerable<T>, pour les propriétés ou les valeurs de retour représentant des collections en lecture seule.

En général, préférez ReadOnlyCollection<T>. Si cela ne répond pas à certaines exigences (par exemple, la collection ne doit pas implémenter IList), utilisez une collection personnalisée en implémentant IEnumerable<T>, ICollection<T> ou IList<T>. Si vous implémentez une collection en lecture seule personnalisée, implémentez ICollection<T>.IsReadOnly pour retourner true.

Dans les cas où vous êtes sûr que le seul scénario que vous souhaiterez prendre en charge est l’itération avant uniquement, vous pouvez simplement utiliser IEnumerable<T>.

✔️ ENVISAGEZ d’utiliser des sous-classes de collections de base génériques au lieu d’utiliser directement les collections.

Cela permet d’obtenir un meilleur nom et d’ajouter des membres d’assistance qui ne sont pas présents sur les types de collection de base. Cela s’applique particulièrement aux API de haut niveau.

✔️ ENVISAGEZ de retourner une sous-classe de Collection<T> ou ReadOnlyCollection<T> à partir des méthodes et propriétés très couramment utilisées.

Cela vous permettra d’ajouter des méthodes d’assistance ou de modifier l’implémentation de la collection à l’avenir.

✔️ ENVISAGEZ d’utiliser une collection à clés si les éléments stockés dans la collection ont des clés uniques (noms, ID, etc.). Les collections à clé sont des collections qui peuvent être indexées à la fois par un entier et une clé, et qui sont généralement implémentées en héritant de KeyedCollection<TKey,TItem>.

Les collections à clé ont généralement un encombrement mémoire plus important et ne doivent pas être utilisées si la surcharge de mémoire l’emporte sur les avantages de disposer des clés.

❌ NE renvoyez PAS de valeurs null à partir des propriétés de collection ou des méthodes qui retournent des collections. Retournez une collection vide ou un tableau vide à la place.

La règle générale est que les collections ou tableaux null et vides (0 élément) doivent être traités de la même façon.

Instantanés et regroupements dynamiques

Les collections représentant un état à un moment donné sont appelées instantanés. Par exemple, une collection contenant des lignes retournées à partir d’une requête de base de données serait un instantané. Les collections qui représentent toujours l’état actuel sont appelées collections actives. Par exemple, une collection d’éléments ComboBox est une collection dynamique.

❌NE renvoyez PAS d’instantanés à partir de propriétés. Les propriétés doivent retourner des collections dynamiques.

Les getters de propriété doivent être des opérations très légères. Le retour d’un instantané nécessite la création d’une copie d’une collection interne dans une opération O(n).

✔️ UTILISEZ un instantané ou un IEnumerable<T> dynamique (ou son sous-type) pour représenter des collections volatiles (c’est-à-dire qui peuvent changer sans modifier explicitement la collection).

En général, toutes les collections représentant une ressource partagée (par exemple, les fichiers d’un répertoire) sont volatiles. De telles collections sont très difficiles ou impossibles à implémenter en tant que collections actives, sauf si l’implémentation est simplement un énumérateur avant uniquement.

Choix entre tableaux et collections

✔️ PRÉFÉREZ les collections aux tableaux.

Les collections offrent plus de contrôle sur le contenu, peuvent évoluer au fil du temps et sont plus utilisables. En outre, l’utilisation de tableaux pour les scénarios en lecture seule est déconseillée, car le coût du clonage des tableaux est prohibitif. Les études de convivialité ont montré que certains développeurs se sentent plus à l’aise avec les API basées sur les collections.

Toutefois, si vous développez des API de bas niveau, il peut être préférable d’utiliser des tableaux pour les scénarios de lecture-écriture. Les tableaux ont un encombrement mémoire plus faible, ce qui permet de réduire le jeu de travail, et l’accès aux éléments d’un tableau est plus rapide, car il est optimisé par le runtime.

✔️ ENVISAGEZ d’utiliser des tableaux dans les API de bas niveau pour réduire la consommation de mémoire et optimiser les performances.

✔️ UTILISEZ des tableaux d’octets au lieu de collections d’octets.

❌ N’utilisez PAS de tableaux pour les propriétés si la propriété doit retourner un nouveau tableau (par exemple, une copie d’un tableau interne) chaque fois que le getter de propriété est appelé.

Implémentation de collections personnalisées

✔️ ENVISAGEZ d’hériter de Collection<T>, ReadOnlyCollection<T> ou KeyedCollection<TKey,TItem> lors de la conception de nouvelles collections.

✔️ IMPLÉMENTEZ IEnumerable<T> lors de la conception de nouvelles collections. Envisagez d’implémenter ICollection<T> ou même IList<T> là où cela a du sens.

Lors de l’implémentation de ce regroupement personnalisé, suivez le modèle d’API établi par Collection<T> et ReadOnlyCollection<T> aussi étroitement que possible. Autrement dit, implémentez explicitement les mêmes membres, nommez les paramètres comme ces deux collections les nomment, et ainsi de suite.

✔️ ENVISAGEZ d’implémenter des interfaces de collection non génériques (IList et ICollection) si la collection sera souvent passée aux API prenant ces interfaces comme entrée.

❌ ÉVITEZ d’implémenter des interfaces de collection sur des types avec des API complexes non liées au concept d’une collection.

❌ N’héritez PAS de collections de base non génériques, comme CollectionBase. Utilisez plutôt Collection<T>, ReadOnlyCollection<T> et KeyedCollection<TKey,TItem>.

Affectation de noms à des collections personnalisées

Les collections (types qui implémentent IEnumerable) sont créées principalement pour deux raisons : (1) pour créer une structure de données avec des opérations spécifiques à la structure et des caractéristiques de performances souvent différentes de celles des structures de données existantes (par exemple, List<T>, LinkedList<T>, Stack<T>) et (2) pour créer une collection spécialisée pour contenir un ensemble spécifique d’éléments (par exemple, StringCollection). Les structures de données sont le plus souvent utilisées dans l’implémentation interne d’applications et de bibliothèques. Les collections spécialisées doivent principalement être exposées dans les API (en tant que types de propriété et de paramètre).

✔️ UTILISEZ le suffixe « Dictionary » dans les noms d’abstractions implémentant IDictionary ou IDictionary<TKey,TValue>.

✔️ UTILISEZ le suffixe « Collection » dans les noms de types implémentant IEnumerable (ou l’un de ses descendants) et représentant une liste d’éléments.

✔️ UTILISEZ le nom de structure de données approprié pour les structures de données personnalisées.

❌ ÉVITEZ d’utiliser des suffixes impliquant une implémentation particulière, comme « LinkedList » ou « Hashtable », dans les noms des abstractions de collection.

✔️ ENVISAGEZ de préfixer les noms de collection avec le nom du type d’élément. Par exemple, une collection stockant des éléments de type Address (implémentant IEnumerable<Address>) doit être nommée AddressCollection. Si le type d’élément est une interface, le préfixe « I » du type d’élément peut être omis. Ainsi, une collection d’éléments IDisposable peut être appelée DisposableCollection.

✔️ ENVISAGEZ d’utiliser le préfixe « ReadOnly » dans les noms des collections en lecture seule si une collection accessible en écriture correspondante peut être ajoutée ou existe déjà dans l’infrastructure.

Par exemple, une collection de chaînes en lecture seule doit être appelée ReadOnlyStringCollection.

Portions © 2005, 2009 Microsoft Corporation. Tous droits réservés.

Réimprimé avec l’autorisation de Pearson Education, Inc. et extrait de Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition par Krzysztof Cwalina et Brad Abrams, publié le 22 octobre 2008 par Addison-Wesley Professional dans le cadre de la série sur le développement Microsoft Windows.

Voir aussi