Retour de 'IList' vs 'ICollection' vs 'Collection'

119

Je ne sais pas quel type de collection je dois renvoyer à partir de mes méthodes et propriétés API publiques.

Les collections que j'ai en tête sont IList, ICollectionet Collection.

Le retour de l'un de ces types est-il toujours préféré aux autres ou dépend-il de la situation spécifique?

Rocky Singh
la source

Réponses:

71

En règle générale, vous devez renvoyer un type le plus général possible, c'est-à-dire qui connaît juste assez les données renvoyées que le consommateur doit utiliser. De cette façon, vous avez une plus grande liberté pour modifier l'implémentation de l'API, sans casser le code qui l'utilise.

Considérez également l' IEnumerable<T>interface comme type de retour. Si le résultat doit être répété, le consommateur n'a pas besoin de plus que cela.

Guffa
la source
25
Mais considérez également ICollection <T> qui étend IEnumerable <T> pour fournir des utilitaires supplémentaires, y compris une propriété Count. Cela peut être utile car vous pouvez déterminer la longueur de la séquence sans la parcourir plusieurs fois.
retrodrone le
11
@retrodrone: En fait, l'implémentation de la Countméthode vérifie le type réel de la collection, elle utilisera donc les propriétés Lengthou Countpour certains types connus comme les tableaux et ICollectionmême lorsque vous le fournissez en tant que fichier IEnumerable.
Guffa
8
@Guffa: Il peut être intéressant de noter que si une classe Thing<T>implémente IList<T>mais pas le non générique ICollection, alors appeler IEnumerable<Cat>.Count()a Thing<Cat>serait rapide, mais l'appel IEnumerable<Animal>.Count()serait lent (puisque la méthode d'extension chercherait, et ne trouverait pas, une implémentation de ICollection<Cat>). Si la classe implémente le non-générique ICollection, cependant, IEnumerable<Animal>.Counttrouvera et l'utilisera.
supercat
8
Je ne suis pas d'accord. Il est préférable de renvoyer le type le plus riche dont vous disposez afin que le client puisse l'utiliser directement si c'est ce qu'il souhaite. Si vous avez un List <>, pourquoi retourner un IEnuerable <> uniquement pour forcer l'appelant à appeler ToList () inutilement?
zumalifeguard
140

ICollection<T>est une interface qui expose la sémantique de collecte , tels que Add(), Remove(), et Count.

Collection<T>est une implémentation concrète de l' ICollection<T>interface.

IList<T>est essentiellement un ICollection<T>accès basé sur un ordre aléatoire.

Dans ce cas, vous devez décider si vos résultats nécessitent ou non une sémantique de liste telle que l'indexation basée sur l'ordre (puis utilisez IList<T>) ou si vous devez simplement renvoyer un "sac" de résultats non ordonné (puis utiliser ICollection<T>).

cordialgerm
la source
5
Collection<T>outils IList<T>et pas seulement ICollection<T>.
CodesInChaos
56
Toutes les collections dans .Net sont ordonnées, car IEnumerable<T>ordonnées. Ce qui distingue IList<T>de ICollection<T>est qu'il offre à ses membres de travailler avec des index. Par exemple list[5]fonctionne, mais collection[5]ne compile pas.
svick
5
Je suis également en désaccord sur le fait que les collections ordonnées devraient être implémentées IList<T>. Il existe de nombreuses collections ordonnées qui ne le font pas. IList<T>concerne l'accès indexé rapide.
CodesInChaos
4
@yoyo Les classes et interfaces de la collection .net sont simplement mal conçues et mal nommées. Je ne penserais pas trop pourquoi ils les ont nommés comme ça.
CodesInChaos
6
@svick: Toutes les collections sont-elles ordonnées parce qu'elles sont IEnumerable<T>commandées? Prenez HashSet<T>- il met en œuvre IEnumerable<T>mais n'est clairement pas commandé. C'est-à-dire que vous n'avez aucune influence sur l'ordre des éléments et que cet ordre peut changer à tout moment, si la table de hachage interne est réorganisée.
Olivier Jacot-Descombes
61

La principale différence entre IList<T>et ICollection<T>est que IList<T>vous permet d'accéder aux éléments via un index. IList<T>décrit les types de type tableau. Les éléments d'un ICollection<T>ne sont accessibles que par énumération. Les deux permettent l'insertion et la suppression d'éléments.

Si vous avez seulement besoin d'énumérer une collection, IEnumerable<T>c'est préférable. Il présente deux avantages par rapport aux autres:

  1. Il interdit les modifications de la collection (mais pas des éléments, s'ils sont de type référence).

  2. Il permet la plus grande variété de sources possible, y compris des énumérations générées par algorithme et qui ne sont pas du tout des collections.

Collection<T>est une classe de base qui est principalement utile aux implémenteurs de collections. Si vous l'exposez dans des interfaces (API), de nombreuses collections utiles qui n'en dérivent pas seront exclues.


Un inconvénient de IList<T>est que les tableaux l'implémentent mais ne vous permettent pas d'ajouter ou de supprimer des éléments (c'est-à-dire que vous ne pouvez pas modifier la longueur du tableau). Une exception sera levée si vous appelez IList<T>.Add(item)un tableau. La situation est quelque peu désamorcée, tout comme IList<T>une propriété booléenne IsReadOnlyque vous pouvez vérifier avant d'essayer de le faire. Mais à mes yeux, c'est encore un défaut de conception dans la bibliothèque . Par conséquent, j'utilise List<T>directement, lorsque la possibilité d'ajouter ou de supprimer des éléments est requise.

Olivier Jacot-Descombes
la source
Analyse du code (et FxCop) recommande que lors du retour d' une collection générique en béton, l' un des éléments suivants doivent être retournés: Collection, ReadOnlyCollectionou KeyedCollection. Et cela Listne devrait pas être retourné.
DavidRR
@DavidRR: Souvent, il est utile de passer une liste à une méthode qui doit ajouter ou supprimer des éléments. Si vous tapez le paramètre comme IList<T>, il n'y a aucun moyen de garantir que la méthode ne sera pas appelée avec un tableau comme paramètre.
Olivier Jacot-Descombes
7

IList<T>est l'interface de base de toutes les listes génériques. Puisqu'il s'agit d'une collection ordonnée, l'implémentation peut décider de l'ordre, allant de l'ordre trié à l'ordre d'insertion. De plus, la Ilistpropriété Item permet aux méthodes de lire et de modifier les entrées de la liste en fonction de leur index. Cela permet d'insérer, de supprimer une valeur dans / de la liste à un index de position.

Depuis IList<T> : ICollection<T>, toutes les méthodes de ICollection<T>sont également disponibles ici pour l'implémentation.

ICollection<T>est l'interface de base de toutes les collections génériques. Il définit la taille, les énumérateurs et les méthodes de synchronisation. Vous pouvez ajouter ou supprimer un élément dans une collection, mais vous ne pouvez pas choisir à quelle position il se trouve en raison de l'absence de propriété d'index.

Collection<T>fournit une implémentation pour IList<T>, IListet IReadOnlyList<T>.

Si vous utilisez un type d'interface plus étroit tel que ICollection<T>au lieu de IList<T>, vous protégez votre code contre les modifications importantes. Si vous utilisez un type d'interface plus large tel que IList<T>, vous risquez davantage d'interrompre les modifications de code.

Citant d'un source ,

ICollection, ICollection<T>: Vous souhaitez modifier la collection ou vous vous souciez de sa taille. IList, IList<T>: Vous souhaitez modifier la collection et vous vous souciez de l'ordre et / ou du positionnement des éléments dans la collection.

NullReference
la source
5

Le retour d'un type d'interface est plus général, donc (faute d'informations supplémentaires sur votre cas d'utilisation spécifique), je pencherais vers cela. Si vous souhaitez exposer la prise en charge de l'indexation, choisissez IList<T>, sinon ICollection<T>cela suffira. Enfin, si vous souhaitez indiquer que les types renvoyés sont en lecture seule, choisissezIEnumerable<T> .

Et, au cas où vous ne l'auriez pas encore lu, Brad Abrams et Krzysztof Cwalina ont écrit un excellent livre intitulé "Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries" (vous pouvez télécharger un résumé à partir d' ici ).

Alan
la source
IEnumerable<T>est en lecture seule? Bien sûr, mais lorsque vous le réaccordez dans un contexte de référentiel, même après avoir exécuté ToList, ce n'est pas thread-safe. Le problème est que lorsque la source de données d'origine obtient GC, IEnumarble.ToList () ne fonctionne pas dans un contexte multi-thread. Cela fonctionne sur un linéaire parce que GC est appelé après que vous ayez fait le travail, mais utiliser asyncmaintenant un jour dans 4.5+ IEnumarbale devient un mal de balle.
Piotr Kula
-3

Il y a quelques sujets qui découlent de cette question:

  • interfaces contre classes
  • quelle classe spécifique, parmi plusieurs classes identiques, collection, liste, tableau?
  • Classes communes et collections de sous-éléments ("génériques")

Vous voudrez peut-être souligner que c'est une API orientée objet

interfaces contre classes

Si vous n'avez pas beaucoup d'expérience avec les interfaces, je vous recommande de vous en tenir aux classes. Je vois souvent des développeurs sauter aux interfaces, même si ce n'est pas nécessaire.

Et, finissez par faire une mauvaise conception d'interface, au lieu d'une bonne conception de classe, qui, d'ailleurs, peut éventuellement être migrée vers une bonne conception d'interface ...

Vous verrez beaucoup d'interfaces dans l'API, mais ne vous précipitez pas, si vous n'en avez pas besoin.

Vous apprendrez éventuellement à appliquer des interfaces à votre code.

quelle classe spécifique, parmi plusieurs classes identiques, collection, liste, tableau?

Il existe plusieurs classes en c # (dotnet) qui peuvent être interchangées. Comme déjà mentionné, si vous avez besoin de quelque chose d'une classe plus spécifique, telle que "CanBeSortedClass", rendez-le explicite dans votre API.

Votre utilisateur d'API a-t-il vraiment besoin de savoir que votre classe peut être triée ou d'appliquer un format aux éléments? Ensuite, utilisez "CanBeSortedClass" ou "ElementsCanBePaintedClass", sinon utilisez "GenericBrandClass".

Sinon, utilisez une classe plus générale.

Classes de collection communes et collections de sous-éléments ("génériques")

Vous constaterez qu'il existe des classes qui contiennent d'autres éléments, et vous pouvez spécifier que tous les éléments doivent être d'un type spécifique.

Les collections génériques sont ces classes que vous pouvez utiliser la même collection, pour plusieurs applications de code, sans avoir à créer une nouvelle collection, pour chaque nouveau type de sous-élément, comme ceci: Collection .

L'utilisateur de votre API va-t-il avoir besoin d'un type très spécifique, identique pour tous les éléments?

Utilisez quelque chose comme List<WashingtonApple>.

Votre utilisateur d'API aura-t-il besoin de plusieurs types connexes?

Exposer List<Fruit>pour votre API, et utiliser List<Orange> List<Banana>, en List<Strawberry>interne, où Orange, Bananaet Strawberrysont les descendants de Fruit.

Votre utilisateur d'API aura-t-il besoin d'une collection de types génériques?

Utilisez List, où se trouvent tous les éléments object.

À votre santé.

umlcat
la source
7
"Si vous n'avez pas beaucoup d'expérience avec les interfaces, je vous recommande de vous en tenir aux classes" Ce n'est pas un bon conseil. C'est comme dire s'il y a quelque chose que vous ne savez pas, éloignez-vous de cela. Les interfaces sont extrêmement puissantes et soutiennent le concept de «programme vers les abstractions contre les concrétions». Elles sont également assez puissantes pour faire des tests unitaires en raison de la possibilité d'échanger des types via des simulations. Il y a un tas d'autres bonnes raisons d'utiliser des interfaces au-delà de ces commentaires, alors veuillez les explorer.
atconway
@atconway J'utilise régulièrement des interfaces, mais, comme beaucoup de concepts, c'est un outil puissant, il peut être facile à utiliser. Et, parfois, le développeur peut ne pas en avoir besoin. Ma suggestion n'était pas "ne jamais utiliser d'interfaces", ma suggestion était "dans ce cas, vous pouvez sauter les interfaces. Apprenez-en davantage et vous pourrez en tirer le meilleur plus tard". À votre santé.
umlcat
1
Je recommande de toujours programmer sur une interface. Cela permet de garder le code faiblement couplé.
mac10688
@ mac10688 Ma réponse précédente (atconway) s'applique également à votre commentaire.
umlcat