126 votes

Retourner 'IList' vs 'ICollection' vs 'Collection'.

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

Les collections auxquelles je pense sont IList , ICollection y Collection .

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

4 votes

0 votes

144voto

pickles Points 4235

ICollection<T> est une interface qui expose la sémantique de la collection telle que Add() , Remove() y Count .

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

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

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 (utilisez alors IList<T> ) ou si vous avez simplement besoin de renvoyer un "sac" non ordonné de résultats (utilisez alors l'option ICollection<T> ).

2 votes

+1 pour une bonne réponse. Cependant, je recommanderais d'ajouter quelques notes sur le principe général de l'abstraction, et pourquoi il favorise le retour de types d'interfaces plutôt que de types de classes concrètes.

5 votes

Collection<T> met en œuvre IList<T> et pas seulement ICollection<T> .

56 votes

Toutes les collections dans .Net sont ordonnées, car IEnumerable<T> est commandée. Ce qui distingue IList<T> de ICollection<T> est qu'il propose aux membres de travailler avec des index. Par exemple list[5] fonctionne, mais collection[5] ne compilera pas.

73voto

Guffa Points 308133

En général, vous devez renvoyer un type aussi général que possible, c'est-à-dire un type qui connaît juste assez de données renvoyées pour que le consommateur puisse les 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 le IEnumerable<T> comme type de retour. Si le résultat ne doit être qu'itéré, le consommateur n'a pas besoin de plus que cela.

29 votes

Mais pensez aussi à ICollection<T> qui étend IEnumerable<T> pour fournir des utilitaires supplémentaires, notamment une propriété Count. Cela peut être utile car vous pouvez déterminer la longueur de la séquence sans la parcourir plusieurs fois.

12 votes

@retrodrone : En fait, l'implémentation de la Count vérifie le type réel de la collection, elle utilisera donc la méthode Length o Count pour certains types connus comme les tableaux et les ICollection même si vous le fournissez en tant que IEnumerable .

8 votes

@Guffa : Il peut être intéressant de noter que si une classe Thing<T> met en œuvre IList<T> mais pas le non générique ICollection puis en appelant IEnumerable<Cat>.Count() sur un Thing<Cat> serait rapide, mais appeler 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 la fonction non générique ICollection Cependant, IEnumerable<Animal>.Count trouverait et utiliserait ça.

67voto

La principale différence entre les IList<T> y ICollection<T> interfaces est que IList<T> 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, alors IEnumerable<T> est à privilégier. 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 autorise la plus grande variété possible de sources, y compris les énumérations générées de manière algorithmique et qui ne sont pas du tout des collections.

  3. Permet d'accéder à évaluation paresseuse et peuvent être interrogés avec LINQ.

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 ne dérivent pas d'elle seront exclues.


L'un des inconvénients 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) sur un tableau. La situation est quelque peu désamorcée car IList<T> a une propriété booléenne IsReadOnly que vous pouvez vérifier avant de tenter de le faire. Mais à mes yeux, il s'agit toujours d'un défaut de conception de la bibliothèque. . Par conséquent, j'utilise List<T> directement, lorsque la possibilité d'ajouter ou de supprimer des éléments est requise.


Lequel dois-je choisir ? Considérons seulement List<T> y IEnumerable<T> comme exemples de types spécialisés/généralisés :

  • Méthode entrée paramètre
    • IEnumerable<T> La plus grande flexibilité pour l'appelant. Restrictif pour l'implémenteur, en lecture seule.
    • List<T> Restrictif pour l'appelant. Donne de la flexibilité à l'implémenteur, qui peut manipuler la collection.
  • Méthode ouput ou valeur de retour
    • IEnumerable<T> Restrictif pour l'appelant, en lecture seule. La plus grande flexibilité pour l'implémenteur. Permet de retourner n'importe quelle collection ou d'implémenter un itérateur ( yield return ).
    • List<T> La plus grande flexibilité pour l'appelant, peut manipuler la collection retournée. Restrictif pour l'implémenteur.

Eh bien, à ce stade, vous pouvez être déçu parce que je ne vous donne pas une réponse simple. Une déclaration comme "toujours utiliser ceci pour l'entrée et cela pour la sortie" ne serait pas constructive. La réalité est que cela dépend du cas d'utilisation. Une méthode comme void AddMissingEntries(TColl collection) devra fournir un type de collection ayant un Add ou peut même exiger une HashSet<T> pour l'efficacité. Une méthode void PrintItems(TColl collection) peuvent vivre heureux avec un IEnumerable<T> .

0 votes

Analyse de code (et FxCop) conseille que lors du retour d'une collection générique concrète, l'un des éléments suivants doit être retourné : Collection , ReadOnlyCollection o KeyedCollection . Et que List ne doit pas être renvoyé.

0 votes

@DavidRR : Il est souvent 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 s'assurer que la méthode ne sera pas appelée avec un tableau comme paramètre.

5voto

Alan Points 3381

Renvoyer un type d'interface est plus général, donc (sans plus d'informations sur votre cas d'utilisation spécifique) je pencherais pour cette option. Si vous voulez exposer le support de l'indexation, choisissez IList<T> sinon ICollection<T> suffira. Enfin, si vous voulez indiquer que les types retournés sont en lecture seule, choisissez IEnumerable<T> .

Et, au cas où vous ne l'auriez pas lu auparavant, 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 de aquí ).

0 votes

IEnumerable<T> est en lecture seule ? Bien sûr, mais quand on le relance 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 originale est appelée par GC, IEnumarble.ToList() ne fonctionne pas dans un contexte multithread. Il fonctionne dans un contexte linéaire parce que la GC est appelée après que vous ayez effectué le travail, mais en utilisant la méthode ToList(), il n'y a pas de sécurité. async Aujourd'hui, dans les versions 4.5+, l'IEnumarbale devient un casse-tête.

-4voto

umlcat Points 2025

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

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

Vous pouvez souligner que c'est un Orienté objet A.P.I.

interfaces et 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 passer aux interfaces, même si ce n'est pas nécessaire.

Et, finir par faire un mauvais design d'interface, au lieu d'un bon design de classe, qui, d'ailleurs, peut éventuellement, être migré vers une bonne conception d'interface ...

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

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

quelle classe spécifique, parmi plusieurs classes similaires, 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, comme "CanBeSortedClass", alors faites-le explicitement dans votre A.P.I..

Est-ce que votre utilisateur A.P.I. a vraiment besoin de savoir que votre classe peut être triée, ou appliquer un certain format aux éléments ? Alors utilisez "CanBeSortedClass" ou "ElementsCanBePaintedClass", sinon, utilisez "GenericBrandClass".

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

Classes de collections 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.

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-article, comme ceci : Collection .

L'utilisateur de votre A.P.I. 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'A.P.I. va-t-il avoir besoin de plusieurs types d'accessoires ?

Exposez List<Fruit> pour ton A.P.I., et utilise List<Orange> List<Banana> , List<Strawberry> en interne, où Orange , Banana y Strawberry sont des descendants de Fruit .

Votre utilisateur A.P.I. va-t-il avoir besoin d'une collection de type générique ?

Utilisez List où tous les éléments sont object (s).

A la vôtre.

7 votes

"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 que s'il y a quelque chose que vous ne connaissez pas, restez-en éloigné. Les interfaces sont extrêmement puissantes et soutiennent le concept de "Programme vers les abstractions vs. les concrétions". Elles sont également très puissantes pour faire des tests unitaires en raison de la possibilité d'échanger des types via les mocks. Il y a un tas d'autres bonnes raisons d'utiliser les interfaces au-delà de ces commentaires, alors n'hésitez pas à les explorer.

0 votes

@atconway J'utilise régulièrement les interfaces, mais, comme beaucoup de concepts, c'est un outil puissant, il peut être facile d'en faire un usage mixte. Et, parfois, le développeur peut ne pas en avoir besoin. Ma suggestion n'était pas "n'utilisez jamais d'interfaces", mais "dans ce cas, vous pouvez ignorer les interfaces. Apprenez-en plus sur le sujet, et vous pourrez en tirer le meilleur parti plus tard". A la vôtre.

1 votes

Je recommande de toujours programmer vers une interface. Cela permet de garder le code faiblement couplé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X