Dois-je toujours retourner IEnumerable <T> au lieu de IList <T>?

98

Lorsque j'écris mon DAL ou un autre code qui renvoie un ensemble d'articles, dois-je toujours faire ma déclaration de retour:

public IEnumerable<FooBar> GetRecentItems()

ou

public IList<FooBar> GetRecentItems()

Actuellement, dans mon code, j'ai essayé d'utiliser IEnumerable autant que possible, mais je ne suis pas sûr que ce soit la meilleure pratique? Cela me semblait juste parce que je renvoyais le type de données le plus générique tout en étant toujours descriptif de ce qu'il fait, mais peut-être que ce n'est pas correct à faire.

KingNestor
la source
1
S'agit-il de List <T> ou IList <T> dont vous parlez? Le titre et la question disent des choses différentes ...
Fredrik Mörk
Lorsque cela est possible, l'interface utilisateur IEnumerable ou Ilist instaead de type concerete.
Usman Masood
2
Je renvoie les collections sous la forme List <T>. Je ne vois pas la nécessité de retourner IEnumberable <T> puisque vous pouvez l'extraire de List <T>
Chuck Conway
duplication possible de ienumerablet-as-return-type
nawfal

Réponses:

45

Cela dépend vraiment de la raison pour laquelle vous utilisez cette interface spécifique.

Par exemple, IList<T>a plusieurs méthodes qui ne sont pas présentes dans IEnumerable<T>:

  • IndexOf(T item)
  • Insert(int index, T item)
  • RemoveAt(int index)

et propriétés:

  • T this[int index] { get; set; }

Si vous avez besoin de ces méthodes de quelque manière que ce soit, revenez certainement IList<T>.

De plus, si la méthode qui consomme votre IEnumerable<T>résultat attend un IList<T>, cela évitera au CLR de prendre en compte les conversions requises, optimisant ainsi le code compilé.

Jon Limjap
la source
2
@Jon FDG recommande d'utiliser Collection <T> ou ReadOnlyCollection <T> comme valeur de retour pour les types de collection voir ma réponse.
Sam Saffron
Vous ne savez pas ce que vous voulez dire dans votre dernière phrase lorsque vous dites "cela sauvera le CLR". Qu'est-ce qui le sauvera, en utilisant IEnumerable vs IList? Pouvez-vous clarifier cela?
PositiveGuy
1
@CoffeeAddict Trois ans après cette réponse, je pense que vous avez raison - cette dernière partie est vague. Si une méthode qui attend un IList <T> comme paramètre obtient un IEnumerable <T>, le IEnumerable doit être encapsulé manuellement dans un nouvel implémenteur de List <T> ou autre IList <T>, et ce travail ne sera pas effectué par le CLR pour vous. Le contraire - une méthode qui attend un IEnumerable <T> obtenant un IList <T>, peut avoir à faire un déballage, mais avec le recul, cela peut ne pas en avoir besoin car IList <T> implémente IEnumerable <T>.
Jon Limjap
69

Les directives de conception de l'infrastructure recommandent d'utiliser la classe Collection lorsque vous devez renvoyer une collection modifiable par l'appelant ou ReadOnlyCollection pour les collections en lecture seule.

La raison pour laquelle cela est préféré à un simple IListest que IListcela n'informe pas l'appelant s'il est en lecture seule ou non.

Si vous renvoyez un à la IEnumerable<T>place, certaines opérations peuvent être un peu plus délicates à effectuer pour l'appelant. De plus, vous ne donnerez plus à l'appelant la possibilité de modifier la collection, ce que vous voudrez peut-être ou non.

Gardez à l'esprit que LINQ contient quelques astuces dans sa manche et optimisera certains appels en fonction du type sur lequel ils sont exécutés. Ainsi, par exemple, si vous effectuez une Countet que la collection sous-jacente est une liste, elle ne parcourra PAS tous les éléments.

Personnellement, pour un ORM, je m'en tiendrai probablement à Collection<T>ma valeur de retour.

Sam Safran
la source
14
Les Directives pour les collections contiennent une liste plus détaillée des DO et DONT.
Chaquotay
27

En général, vous devez exiger le plus générique et renvoyer la chose la plus spécifique possible. Donc, si vous avez une méthode qui prend un paramètre et que vous n'avez vraiment besoin que de ce qui est disponible dans IEnumerable, cela devrait être votre type de paramètre. Si votre méthode peut renvoyer un IList ou un IEnumerable, préférez renvoyer IList. Cela garantit qu'il est utilisable par le plus large éventail de consommateurs.

Soyez lâche dans ce dont vous avez besoin et explicite dans ce que vous fournissez.

Mel
la source
1
J'en suis venu à la conclusion opposée: qu'il faut accepter des types spécifiques et renvoyer des types généraux. Vous avez peut-être raison cependant de dire que des méthodes plus largement applicables sont meilleures que des méthodes plus contraintes. Je vais devoir y réfléchir davantage.
Dave Cousineau
3
La justification de l'acceptation de types généraux en tant qu'entrée est que cela vous permet de travailler avec la plus large gamme d'entrées possible afin d'obtenir le plus de réutilisation possible d'un composant. D'un autre côté, puisque vous savez déjà exactement quel type d'objet vous avez sous la main, il ne sert à rien de le masquer.
Mel du
1
Je pense que je suis d'accord pour avoir des paramètres plus généraux, mais quel est votre raisonnement pour renvoyer quelque chose de moins général?
Dave Cousineau
7
D'accord, laissez-moi essayer ceci d'une autre manière. Pourquoi jeteriez-vous des informations? Si vous ne vous souciez que du résultat étant IEnumerable <T>, cela vous fait-il mal de savoir que c'est un IList <T>? Non, ce n'est pas le cas. Il peut s'agir d'informations superflues dans certains cas, mais cela ne vous fait aucun mal. Maintenant pour le bénéfice. Si vous renvoyez une liste ou une IList, je peux dire immédiatement que la collection a déjà été récupérée, ce que je ne peux pas savoir avec un IEnumerable. Cela peut ou non être des informations utiles, mais encore une fois, pourquoi jeteriez-vous des informations? Si vous connaissez des informations supplémentaires sur quelque chose, transmettez-les.
Mel
Rétrospectivement, je suis surpris que personne ne m'ait jamais appelé à ce sujet. Un IEnumerable aurait déjà été récupéré. Un IQueryable peut cependant être retourné dans un état non énuméré. Alors peut-être qu'un meilleur exemple serait de retourner IQueryable vs IEnumerable. Le renvoi de IQueryable plus spécifique permettrait une composition supplémentaire, tandis que le renvoi de IEnumerable forcerait une énumération immédiate et diminuerait la composabilité de la méthode.
Mel
23

Ça dépend...

Renvoyer le type le moins dérivé ( IEnumerable) vous laissera la plus grande marge de manœuvre pour modifier l'implémentation sous-jacente sur la piste.

Le renvoi d'un type plus dérivé ( IList) fournit aux utilisateurs de votre API plus d'opérations sur le résultat.

Je suggérerais toujours de renvoyer le type le moins dérivé contenant toutes les opérations dont vos utilisateurs auront besoin ... Donc, fondamentalement, vous devez d'abord déterminer quelles opérations sur le résultat ont du sens dans le contexte de l'API que vous définissez.

jerryjvl
la source
belle réponse générique car elle s'applique également à d'autres méthodes.
user420667
1
Je sais que cette réponse est très ancienne ... mais elle semble contredire la documentation: "Si ni l'interface IDictionary <TKey, TValue> ni l'interface IList <T> ne répondent aux exigences de la collection requise, dérivez la nouvelle classe de collection de l'interface ICollection <T> à la place pour plus de flexibilité "Cela semble impliquer que le type le plus dérivé devrait être préféré. ( msdn.microsoft.com/en-us/library/92t2ye13(v=vs.110).aspx ) \
DeborahK
11

Une chose à considérer est que si vous utilisez une instruction LINQ à exécution différée pour générer votre IEnumerable<T>, appeler .ToList()avant de revenir de votre méthode signifie que vos éléments peuvent être itérés deux fois - une fois pour créer la liste, et une fois lorsque l'appelant fait une boucle. , filtre ou transforme votre valeur de retour. Lorsque cela est possible, j'aime éviter de convertir les résultats de LINQ-to-Objects en une liste ou un dictionnaire concret jusqu'à ce que j'aie à le faire. Si mon appelant a besoin d'une liste, c'est un simple appel de méthode facile - je n'ai pas besoin de prendre cette décision pour lui, et cela rend mon code légèrement plus efficace dans les cas où l'appelant fait juste un foreach.

Joel Mueller
la source
@Joel Mueller, j'appellerais généralement ToList () sur eux de toute façon. Je n'aime généralement pas exposer IQueryable au reste de mes projets.
KingNestor
4
Je faisais davantage référence à LINQ-to-Objects, où IQueryable n'entre généralement pas dans l'image. Lorsqu'une base de données est impliquée, ToList () devient plus nécessaire, car sinon vous risquez de fermer votre connexion avant d'itérer, ce qui ne fonctionne pas très bien. Cependant, lorsque ce n'est pas un problème, il est assez facile d'exposer IQueryable en tant que IEnumerable sans forcer une itération supplémentaire lorsque vous souhaitez masquer IQueryable.
Joel Mueller le
9

List<T>offre au code appelant de nombreuses autres fonctionnalités, telles que la modification de l'objet retourné et l'accès par index. La question se résume donc à: dans le cas d'utilisation spécifique de votre application, VOULEZ-VOUS prendre en charge de telles utilisations (vraisemblablement en renvoyant une collection fraîchement construite!), Pour la commodité de l'appelant - ou voulez-vous de la vitesse pour le cas simple lorsque tout le l'appelant a besoin de parcourir la collection et vous pouvez en toute sécurité retourner une référence à une collection sous-jacente réelle sans craindre que cela ne la modifie par erreur, etc.?

Vous seul pouvez répondre à cette question, et seulement en comprenant bien ce que vos appelants voudront faire avec la valeur de retour, et à quel point les performances sont importantes ici (quelle est la taille des collections que vous copieriez, quelle est la probabilité que cela soit un goulot d'étranglement, etc).

Alex Martelli
la source
"renvoyer en toute sécurité une référence à une collection sous-jacente réelle sans craindre que cela ne la modifie par erreur" - Même si vous retournez IEnumerable <T>, ne pourraient-ils pas simplement le renvoyer dans un List <T> et le changer?
Kobi
Tous les IEnumarable <T> ne sont pas également des List <T>. Si l'objet retourné n'est pas d'un type qui hérite de List <T> ou implémente IList <T>, cela entraînera une InvalidCastException.
lowglider le
2
List <T> a le problème qu'il vous verrouille dans une implémentation particulière Collection <T> ou ReadOnlyCollection <T> sont préférés
Sam Saffron
4

Je pense que vous pouvez utiliser l'un ou l'autre, mais chacun a une utilité. Au fond Listest , IEnumerablemais vous devez compter les fonctionnalités, élément add, élément de suppression

IEnumerable n'est pas efficace pour compter les éléments

Si la collection est destinée à être en lecture seule, ou si la modification de la collection est contrôlée par le, Parentrenvoyer un IListjuste pour Countn'est pas une bonne idée.

Dans Linq, il existe une Count()méthode d'extension sur IEnumerable<T>laquelle à l'intérieur du CLR un raccourci vers .Countsi le type sous-jacent est de IList, donc la différence de performances est négligeable.

En général, je pense (opinion) qu'il est préférable de renvoyer IEnumerable lorsque cela est possible, si vous avez besoin de faire des ajouts, ajoutez ces méthodes à la classe parente, sinon le consommateur gère alors la collection dans Model qui viole les principes, par exemple manufacturer.Models.Add(model)viole la loi de demeter. Bien sûr, ce ne sont que des directives et non des règles strictes et rapides, mais jusqu'à ce que vous ayez une pleine compréhension de l'applicabilité, suivre aveuglément est mieux que de ne pas suivre du tout.

public interface IManufacturer 
{
     IEnumerable<Model> Models {get;}
     void AddModel(Model model);
}

(Remarque: si vous utilisez nNHibernate, vous devrez peut-être mapper vers IList privé en utilisant différents accesseurs.)

Utilisateur SO
la source
2

Ce n'est pas si simple lorsque vous parlez de valeurs de retour au lieu de paramètres d'entrée. Lorsqu'il s'agit d'un paramètre d'entrée, vous savez exactement ce que vous devez faire. Donc, si vous avez besoin de pouvoir parcourir la collection, vous prenez un IEnumberable alors que si vous devez ajouter ou supprimer, vous prenez un IList.

Dans le cas d'une valeur de retour, c'est plus difficile. Qu'attend votre interlocuteur? Si vous retournez un IEnumerable, alors il ne saura pas a priori qu'il peut en faire un IList. Mais, si vous renvoyez un IList, il saura qu'il peut le parcourir. Donc, vous devez prendre en compte ce que votre appelant va faire avec les données. La fonctionnalité dont votre appelant a besoin / attend est ce qui devrait régir lors de la décision de retour.

JP Alioto
la source
0

comme tous l'ont dit, cela dépend, si vous ne voulez pas ajouter / supprimer de fonctionnalité lors de l'appel de la couche, je voterai pour IEnumerable car il ne fournit que des itérations et des fonctionnalités de base qui, en perspective de conception, me plaisent. Retournant IList mes votes sont toujours à nouveau mais c'est principalement ce que vous aimez et ce qui ne l'est pas. en termes de performances, je pense qu'ils sont plus identiques.

Usman Masood
la source
0

Si vous ne comptez pas dans votre code externe, il est toujours préférable de renvoyer IEnumerable, car plus tard, vous pouvez modifier votre implémentation (sans impact sur le code externe), par exemple, pour générer une logique d' itérateur et conserver les ressources mémoire (très bonne fonctionnalité de langage d'ailleurs ).

Cependant, si vous avez besoin d'un nombre d'éléments, n'oubliez pas qu'il existe une autre couche entre IEnumerable et IList - ICollection .

arbitre
la source
0

Je suis peut-être un peu en retrait, voyant que personne d'autre ne l'a suggéré jusqu'à présent, mais pourquoi ne pas retourner un (I)Collection<T>?

D'après ce dont je me souviens, Collection<T>le type de retour était préféré List<T>car il fait abstraction de l'implémentation. Ils mettent tous en œuvre IEnumerable, mais cela me semble un peu trop bas pour le travail.

Tiberiu Ana
la source
0

Je pense que vous pouvez utiliser l'un ou l'autre, mais chacun a une utilité. Au fond Listest , IEnumerablemais vous devez compter la fonctionnalité, l' élément Ajouter, supprimer élément

IEnumerable n'est pas efficace pour compter les éléments ou obtenir un élément spécifique dans la collection.

List est une collection parfaitement adaptée à la recherche d'éléments spécifiques, facile à ajouter ou à supprimer.

En général, j'essaye d'utiliser Listlà où c'est possible car cela me donne plus de flexibilité.

Utilisez List<FooBar> getRecentItems() plutôt que IList<FooBar> GetRecentItems()

Stuart
la source
0

Je pense que la règle générale est d'utiliser la classe la plus spécifique pour revenir, pour éviter de faire un travail inutile et donner plus d'options à votre appelant.

Cela dit, je pense qu'il est plus important de considérer le code que vous écrivez devant vous que le code que le prochain écrira (dans des limites raisonnables). C'est parce que vous pouvez faire des hypothèses sur le code qui existe déjà.

N'oubliez pas que déplacer UP vers une collection de IEnumerable dans une interface fonctionnera, descendre vers IEnumerable à partir d'une collection cassera le code existant.

Si ces opinions semblent toutes contradictoires, c'est que la décision est subjective.

Sprague
la source
0

TL; DR; - résumé

  • Si vous développez un logiciel interne, utilisez le type spécifique (Like List) pour les valeurs de retour et le type le plus générique pour les paramètres d'entrée, même en cas de collections.
  • Si une méthode fait partie de l'API publique d'une bibliothèque redistribuable, utilisez des interfaces au lieu de types de collection concrets pour introduire à la fois les valeurs de retour et les paramètres d'entrée.
  • Si une méthode renvoie une collection en lecture seule, montrez-la en utilisant IReadOnlyListou IReadOnlyCollectioncomme type de valeur de retour.

Plus

Ali Bayat
la source