Quelqu'un peut-il m'expliquer pourquoi je voudrais utiliser IList sur List en C #?
Question connexe: pourquoi est-il jugé mauvais d'exposerList<T>
Quelqu'un peut-il m'expliquer pourquoi je voudrais utiliser IList sur List en C #?
Question connexe: pourquoi est-il jugé mauvais d'exposerList<T>
Réponses:
Si vous exposez votre classe via une bibliothèque que d'autres utiliseront, vous voulez généralement l'exposer via des interfaces plutôt que des implémentations concrètes. Cela vous aidera si vous décidez de modifier l'implémentation de votre classe ultérieurement pour utiliser une classe concrète différente. Dans ce cas, les utilisateurs de votre bibliothèque n'auront pas besoin de mettre à jour leur code car l'interface ne change pas.
Si vous l'utilisez uniquement en interne, vous ne vous en souciez peut-être pas beaucoup et l'utilisation List<T>
peut être acceptable.
La réponse la moins populaire est que les programmeurs aiment prétendre que leur logiciel va être réutilisé dans le monde entier, alors qu'en fait la majorité des projets seront maintenus par un petit nombre de personnes et quelles que soient les belles extraits sonores liés à l'interface, vous vous trompez toi même.
Astronautes d'architecture . Les chances que vous écriviez jamais votre propre IList qui ajoute quelque chose à celles déjà dans le framework .NET sont si éloignées que ce sont des gelées théoriques réservées aux "meilleures pratiques".
Évidemment, si on vous demande ce que vous utilisez dans une interview, vous dites IList, souriez, et les deux vous semblent heureux d'être si intelligents. Ou pour une API publique, IList. J'espère que vous comprendrez mon point.
IEnumerable<string>
, à l'intérieur de la fonction, je peux utiliser un List<string>
pour un magasin de sauvegarde interne pour générer ma collection, mais je veux seulement que les appelants énumèrent son contenu, pas l'ajouter ou le supprimer. Accepter une interface comme paramètre communique un message similaire "J'ai besoin d'une collection de chaînes, ne vous inquiétez pas, je ne la changerai pas."
L'interface est une promesse (ou un contrat).
Comme toujours avec les promesses - plus c'est petit, mieux c'est .
IList
promesse aussi. Quelle est la plus petite et la meilleure promesse ici? Ce n'est pas logique.
List<T>
, car AddRange
n'est pas défini sur l'interface.
IList<T>
fait des promesses qu'il ne peut tenir. Par exemple, il existe une Add
méthode que peut lancer s'il IList<T>
se trouve être en lecture seule. List<T>
d'autre part est toujours accessible en écriture et tient donc toujours ses promesses. De plus, depuis .NET 4.6, nous avons maintenant IReadOnlyCollection<T>
et IReadOnlyList<T>
qui sont presque toujours mieux adaptés que IList<T>
. Et ils sont covariants. A éviter IList<T>
!
IList<T>
. Quelqu'un pourrait tout aussi bien implémenter IReadOnlyList<T>
et faire en sorte que chaque méthode lève également une exception. C'est un peu le contraire de la frappe de canard - vous l'avez appelé un canard, mais il ne peut pas charlatan comme un canard. Cela fait cependant partie du contrat, même si le compilateur ne peut pas l'appliquer. Je fais 100% conviennent que IReadOnlyList<T>
ou IReadOnlyCollection<T>
doit être utilisé pour l' accès en lecture seule, cependant.
Certaines personnes disent "toujours utiliser IList<T>
au lieu de List<T>
".
Ils veulent que vous changiez vos signatures de méthode de void Foo(List<T> input)
à void Foo(IList<T> input)
.
Ces gens ont tort.
C'est plus nuancé que ça. Si vous renvoyez un IList<T>
élément de l'interface publique à votre bibliothèque, vous vous laissez des options intéressantes pour peut-être faire une liste personnalisée à l'avenir. Vous n'aurez peut-être jamais besoin de cette option, mais c'est un argument. Je pense que c'est l'argument entier pour retourner l'interface au lieu du type concret. Il convient de le mentionner, mais dans ce cas, il présente un grave défaut.
À titre de contre-argument mineur, vous pouvez trouver que chaque appelant en a besoin de List<T>
toute façon, et le code d'appel est jonché de.ToList()
Mais bien plus important encore, si vous acceptez une IList comme paramètre, vous feriez mieux de faire attention, car IList<T>
et List<T>
ne vous comportez pas de la même manière. Malgré la similitude de nom, et malgré le partage d'une interface, ils n'exposent pas le même contrat .
Supposons que vous ayez cette méthode:
public Foo(List<int> a)
{
a.Add(someNumber);
}
Un collègue utile "refactorise" la méthode à accepter IList<int>
.
Votre code est maintenant cassé, car int[]
implémente IList<int>
, mais est de taille fixe. Le contrat pour ICollection<T>
(la base de IList<T>
) requiert le code qui l'utilise pour vérifier l' IsReadOnly
indicateur avant d'essayer d'ajouter ou de supprimer des éléments de la collection. Le contrat List<T>
ne l'est pas.
Le principe de substitution de Liskov (simplifié) stipule qu'un type dérivé doit pouvoir être utilisé à la place d'un type de base, sans conditions préalables ni postconditions supplémentaires.
On dirait que cela rompt le principe de substitution de Liskov.
int[] array = new[] {1, 2, 3};
IList<int> ilist = array;
ilist.Add(4); // throws System.NotSupportedException
ilist.Insert(0, 0); // throws System.NotSupportedException
ilist.Remove(3); // throws System.NotSupportedException
ilist.RemoveAt(0); // throws System.NotSupportedException
Mais ce n'est pas le cas. La réponse à cela est que l'exemple utilisé IList <T> / ICollection <T> est incorrect. Si vous utilisez un ICollection <T>, vous devez vérifier l'indicateur IsReadOnly.
if (!ilist.IsReadOnly)
{
ilist.Add(4);
ilist.Insert(0, 0);
ilist.Remove(3);
ilist.RemoveAt(0);
}
else
{
// what were you planning to do if you were given a read only list anyway?
}
Si quelqu'un vous passe un tableau ou une liste, votre code fonctionnera bien si vous vérifiez le drapeau à chaque fois et avez un repli ... Mais vraiment; qui fait ça? Ne savez-vous pas à l'avance si votre méthode a besoin d'une liste pouvant accueillir des membres supplémentaires; ne spécifiez-vous pas cela dans la signature de la méthode? Que feriez-vous exactement si vous passiez une liste en lecture seule comme int[]
?
Vous pouvez remplacer un List<T>
code into qui utilise IList<T>
/ ICollection<T>
correctement . Vous ne pouvez pas garantir que vous pouvez remplacer un code IList<T>
/ ICollection<T>
into qui utilise List<T>
.
Il y a un appel au principe de responsabilité unique / principe de ségrégation d'interface dans de nombreux arguments pour utiliser des abstractions au lieu de types concrets - dépendent de l'interface la plus étroite possible. Dans la plupart des cas, si vous utilisez un List<T>
et que vous pensez que vous pourriez utiliser une interface plus étroite à la place - pourquoi pas IEnumerable<T>
? C'est souvent un meilleur ajustement si vous n'avez pas besoin d'ajouter des éléments. Si vous devez ajouter à la collection, utilisez le type de béton, List<T>
.
Pour moi IList<T>
(et ICollection<T>
) c'est la pire partie du framework .NET. IsReadOnly
viole le principe de la moindre surprise. Une classe, telle que Array
, qui ne permet jamais d'ajouter, d'insérer ou de supprimer des éléments ne doit pas implémenter une interface avec les méthodes Add, Insert et Remove. (voir également /software/306105/implementing-an-interface-when-you-dont-need-one-of-the-properties )
Est-ce IList<T>
un bon choix pour votre organisation? Si un collègue vous demande de modifier une signature de méthode à utiliser à la IList<T>
place de List<T>
, demandez-lui comment il ajouterait un élément à un IList<T>
. S'ils ne savent pas IsReadOnly
(et la plupart des gens ne le savent pas), alors ne l'utilisez pas IList<T>
. Déjà.
Notez que l'indicateur IsReadOnly provient de ICollection <T> et indique si des éléments peuvent être ajoutés ou supprimés de la collection; mais juste pour vraiment confondre les choses, cela n'indique pas si elles peuvent être remplacées, ce qui peut être le cas dans le cas des tableaux (qui retournent IsReadOnlys == true).
Pour plus d'informations sur IsReadOnly, voir la définition msdn de ICollection <T> .IsReadOnly
IsReadOnly
c'est true
pour un IList
, vous pouvez toujours modifier ses membres actuels! Peut-être que cette propriété devrait être nommée IsFixedSize
?
Array
comme un IList
, c'est pourquoi j'ai pu modifier les membres actuels)
IReadOnlyCollection<T>
et IReadOnlyList<T>
qui sont presque toujours mieux adaptés que IList<T>
mais sans la sémantique paresseuse dangereuse de IEnumerable<T>
. Et ils sont covariants. A éviter IList<T>
!
List<T>
est une implémentation spécifique de IList<T>
, qui est un conteneur qui peut être adressé de la même manière qu'un tableau linéaire en T[]
utilisant un index entier. Lorsque vous spécifiez IList<T>
comme type d'argument de la méthode, vous spécifiez uniquement que vous avez besoin de certaines capacités du conteneur.
Par exemple, la spécification d'interface n'applique pas une structure de données spécifique à utiliser. L'implémentation de List<T>
arrive aux mêmes performances pour accéder, supprimer et ajouter des éléments en tant que tableau linéaire. Cependant, vous pourriez imaginer une implémentation qui est soutenue par une liste chaînée à la place, pour laquelle ajouter des éléments à la fin est moins cher (temps constant) mais l'accès aléatoire beaucoup plus cher. (Notez que le .NET LinkedList<T>
ne pas mettre en œuvre IList<T>
.)
Cet exemple vous indique également qu'il peut y avoir des situations où vous devez spécifier l'implémentation, et non l'interface, dans la liste des arguments: Dans cet exemple, chaque fois que vous avez besoin d'une caractéristique de performance d'accès particulière. Ceci est généralement garanti pour une implémentation spécifique d'un conteneur ( List<T>
documentation: "Il implémente l' IList<T>
interface générique à l'aide d'un tableau dont la taille est dynamiquement augmentée selon les besoins.").
En outre, vous souhaiterez peut-être envisager d'exposer le moins de fonctionnalités dont vous avez besoin. Par exemple. si vous n'avez pas besoin de modifier le contenu de la liste, vous devriez probablement envisager d'utiliser IEnumerable<T>
, qui IList<T>
s'étend.
LinkedList<T>
vs prendre List<T>
vs IList<T>
tous communiquent quelque chose des garanties de performance requises par le code appelé.
Je tournerais la question un peu, au lieu de justifier pourquoi vous devriez utiliser l'interface sur l'implémentation concrète, essayez de justifier pourquoi vous utiliseriez l'implémentation concrète plutôt que l'interface. Si vous ne pouvez pas le justifier, utilisez l'interface.
IList <T> est une interface qui vous permet d'hériter d'une autre classe et d'implémenter IList <T> tout en héritant de List <T> vous en empêche.
Par exemple, s'il existe une classe A et que votre classe B en hérite, vous ne pouvez pas utiliser List <T>
class A : B, IList<T> { ... }
Un principe de TDD et OOP est généralement la programmation vers une interface et non une implémentation.
Dans ce cas spécifique, puisque vous parlez essentiellement d'une construction de langage, pas d'une construction personnalisée, cela n'a généralement pas d'importance, mais dites par exemple que vous avez trouvé que List ne supportait pas quelque chose dont vous aviez besoin. Si vous aviez utilisé IList dans le reste de l'application, vous pourriez étendre List avec votre propre classe personnalisée et être en mesure de le transmettre sans refactoring.
Le coût pour ce faire est minime, pourquoi ne pas vous épargner les maux de tête plus tard? C'est le principe de l'interface.
public void Foo(IList<Bar> list)
{
// Do Something with the list here.
}
Dans ce cas, vous pouvez passer dans n'importe quelle classe qui implémente l'interface IList <Bar>. Si vous avez utilisé List <Bar> à la place, seule une instance de List <Bar> a pu être transmise.
La méthode IList <Bar> est plus faiblement couplée que la méthode List <Bar>.
Supposant qu'aucune de ces questions (ou réponses) List vs IList ne mentionne la différence de signature. (C'est pourquoi j'ai cherché cette question sur SO!)
Voici donc les méthodes contenues dans List qui ne se trouvent pas dans IList, au moins à partir de .NET 4.5 (vers 2015)
Reverse
et ToArray
sont des méthodes d'extension pour l'interface IEnumerable <T>, dont IList <T> dérive.
List
s et non dans d'autres implémentations de IList
.
Le cas le plus important pour l'utilisation d'interfaces sur des implémentations réside dans les paramètres de votre API. Si votre API prend un paramètre List, toute personne qui l'utilise doit utiliser List. Si le type de paramètre est IList, l'appelant dispose de beaucoup plus de liberté et peut utiliser des classes dont vous n'avez jamais entendu parler, qui n'existaient peut-être même pas lors de l'écriture de votre code.
Que faire si .NET 5.0 remplace System.Collections.Generic.List<T>
à System.Collection.Generics.LinearList<T>
. .NET possède toujours le nom List<T>
mais ils garantissent qu'il IList<T>
s'agit d'un contrat. Donc, à mon humble avis, nous (au moins I) ne sommes pas censés utiliser le nom de quelqu'un (bien qu'il s'agisse de .NET dans ce cas) et d'avoir des problèmes plus tard.
En cas d'utilisation IList<T>
, l'appelant est toujours assuré que les choses fonctionnent, et l'implémenteur est libre de changer la collection sous-jacente en n'importe quelle implémentation concrète alternative deIList
Tous les concepts sont essentiellement énoncés dans la plupart des réponses ci-dessus concernant pourquoi utiliser l'interface sur des implémentations concrètes.
IList<T> defines those methods (not including extension methods)
IList<T>
Lien MSDN
List<T>
implémente ces neuf méthodes (sans compter les méthodes d'extension), en plus de cela, il dispose d'environ 41 méthodes publiques, ce qui pèse dans votre considération sur celle à utiliser dans votre application.
List<T>
Lien MSDN
Vous le feriez parce que la définition d'une IList ou d'une ICollection s'ouvrirait pour d'autres implémentations de vos interfaces.
Vous voudrez peut-être avoir un IOrderRepository qui définit une collection de commandes dans IList ou ICollection. Vous pouvez alors avoir différents types d'implémentations pour fournir une liste de commandes tant qu'elles sont conformes aux "règles" définies par votre IList ou ICollection.
IList <> est presque toujours préférable selon les conseils de l'autre affiche, mais notez qu'il y a un bogue dans .NET 3.5 sp 1 lors de l'exécution d'une IList <> à travers plus d'un cycle de sérialisation / désérialisation avec WCF DataContractSerializer.
Il existe maintenant un SP pour corriger ce bogue: KB 971030
L'interface garantit que vous obtenez au moins les méthodes que vous attendez ; être conscient de la définition de l'interface ie. toutes les méthodes abstraites qui doivent être implémentées par n'importe quelle classe héritant de l'interface. donc si quelqu'un crée sa propre classe avec plusieurs méthodes en plus de celles qu'il a héritées de l'interface pour certaines fonctionnalités supplémentaires, et celles-ci ne vous sont d'aucune utilité, il vaut mieux utiliser une référence à une sous-classe (dans ce cas, le interface) et lui affecter l'objet de classe concret.
l'avantage supplémentaire est que votre code est à l'abri de toute modification de la classe concrète car vous n'êtes abonné qu'à quelques-unes des méthodes de la classe concrète et ce sont celles qui vont être là tant que la classe concrète hérite de l'interface que vous êtes en utilisant. donc sa sécurité pour vous et sa liberté pour le codeur qui écrit une implémentation concrète pour changer ou ajouter plus de fonctionnalités à sa classe concrète.
Vous pouvez regarder cet argument sous plusieurs angles, y compris celui d'une approche purement OO qui dit de programmer contre une interface et non une implémentation. Avec cette pensée, l'utilisation d'IList suit le même principe que le passage et l'utilisation des interfaces que vous définissez à partir de zéro. Je crois également aux facteurs d'évolutivité et de flexibilité fournis par une interface en général. Si une classe implémentant IList <T> doit être étendue ou modifiée, le code consommateur ne doit pas changer; il sait à quoi adhère le contrat IList Interface. Cependant, l'utilisation d'une implémentation concrète et de List <T> sur une classe qui change, pourrait également entraîner la modification du code appelant. En effet, une classe adhérant à IList <T> garantit un certain comportement qui n'est pas garanti par un type concret utilisant List <T>.
Le fait de pouvoir également modifier l'implémentation par défaut de List <T> sur une classe implémentant IList <T>, par exemple le .Add, .Remove ou toute autre méthode IList, donne au développeur beaucoup de flexibilité et de puissance, sinon prédéfini par liste <T>
En règle générale, une bonne approche consiste à utiliser IList dans votre API accessible au public (le cas échéant, et la sémantique de liste est nécessaire), puis List en interne pour implémenter l'API. Cela vous permet de passer à une implémentation différente d'IList sans casser le code qui utilise votre classe.
La liste des noms de classe peut être modifiée dans le prochain framework .net mais l'interface ne changera jamais car l'interface est un contrat.
Notez que, si votre API ne sera utilisée que dans les boucles foreach, etc., vous voudrez peut-être envisager de simplement exposer IEnumerable à la place.