Quand utiliser IList et quand utiliser List

180

Je sais que IList est l'interface et List est le type concret mais je ne sais toujours pas quand utiliser chacun d'eux. Ce que je fais maintenant, c'est que si je n'ai pas besoin des méthodes Sort ou FindAll, j'utilise l'interface. Ai-je raison? Existe-t-il une meilleure façon de décider quand utiliser l'interface ou le type de béton?

Rismo
la source
1
Si quelqu'un se demande encore, je trouve les meilleures réponses ici: stackoverflow.com/questions/400135/listt-or-ilistt
Crismogram

Réponses:

175

Il y a deux règles que je suis:

  • Acceptez le type le plus basique qui fonctionnera
  • Renvoie le type le plus riche dont votre utilisateur aura besoin

Ainsi, lorsque vous écrivez une fonction ou une méthode qui prend une collection, écrivez-la non pas pour prendre un List, mais un IList <T>, un ICollection <T> ou IEnumerable <T>. Les interfaces génériques fonctionneront toujours même pour les listes hétérogènes car System.Object peut également être un T. Cela vous évitera des maux de tête si vous décidez d'utiliser une pile ou une autre structure de données plus tard. Si tout ce que vous avez à faire dans la fonction est de la parcourir, IEnumerable <T> est vraiment tout ce que vous devriez demander.

D'autre part, lorsque vous renvoyez un objet hors d'une fonction, vous souhaitez donner à l'utilisateur l'ensemble d'opérations le plus riche possible sans qu'il ait à effectuer un transfert. Donc, dans ce cas, s'il s'agit d'un List <T> en interne, renvoyez une copie en tant que List <T>.

Lee
la source
43
Vous ne devez pas traiter les types d'entrée / sortie différemment. Les types d'entrée et de sortie doivent tous deux être le type le plus basique (de préférence l'interface) qui répondra aux besoins des clients. L'encapsulation consiste à informer les clients le moins possible de l'implémentation de votre classe. Si vous retournez une liste concrète, vous ne pouvez pas passer à un autre type meilleur sans forcer tous vos clients à recompiler / mettre à jour.
Ash
11
Je ne suis pas d'accord avec les 2 règles ... J'utiliserais le type le plus primitif et spécialement lors du retour dans ce cas IList (mieux IEnumarable) et vous devriez travailler avec List dans votre fonction à l'intérieur. Ensuite, lorsque vous avez besoin d '«ajouter» ou de «trier», utilisez Collection si besoin de plus, puis utilisez Liste. Donc ma règle dure serait: COMMENCEZ toujours avec IENumarable et si vous avez besoin de plus alors prolongez ...
ethem
2
Pour votre commodité, les «deux règles» ont un nom: le principe de robustesse (alias la loi de Postel) .
easoncxz
Quel que soit le côté du débat sur la question de savoir s'il faut renvoyer le type le plus basique ou le type le plus riche, il faut tenir compte du fait que lors du retour d'une interface très simplifiée, le code consommateur peut souvent - mais pas toujours - utiliser une if...elsechaîne avec le ismot - clé pour figurer un type beaucoup plus riche pour cela et finissent par le lancer et l'utiliser de toute façon. Vous n'êtes donc pas nécessairement assuré de cacher quoi que ce soit en utilisant une interface de base, au lieu de simplement l'obscurcir. Cependant, rendre la tâche plus difficile peut également amener l'auteur du code à réfléchir à deux fois à la façon dont il l'utilise.
Panzercrisis
6
Je ne suis pas du tout d'accord sur le point n ° 2, surtout s'il s'agit d'une limite de service / api. Le retour de collections modifiables peut donner l'impression que les collections sont "en direct" et appelant des méthodes comme Add()et Remove()peuvent avoir des effets au-delà de la simple collection. Renvoyer une interface en lecture seule, comme IEnumerablec'est souvent le cas pour les méthodes de récupération de données. Votre consommateur peut le projeter dans un type plus riche selon ses besoins.
STW
56

Les directives Microsoft vérifiées par FxCop découragent l'utilisation de List <T> dans les API publiques - préférez IList <T>.

Incidemment, je déclare maintenant presque toujours les tableaux unidimensionnels comme IList <T>, ce qui signifie que je peux systématiquement utiliser la propriété IList <T> .Count plutôt que Array.Length. Par exemple:

public interface IMyApi
{
    IList<int> GetReadOnlyValues();
}

public class MyApiImplementation : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        List<int> myList = new List<int>();
        ... populate list
        return myList.AsReadOnly();
    }
}
public class MyMockApiImplementationForUnitTests : IMyApi
{
    public IList<int> GetReadOnlyValues()
    {
        IList<int> testValues = new int[] { 1, 2, 3 };
        return testValues;
    }
}
Joe
la source
3
J'aime le plus cette explication / cet exemple!
JonH
28

Il y a une chose importante que les gens semblent toujours oublier:

Vous pouvez passer un tableau simple à quelque chose qui accepte un IList<T>paramètre, puis vous pouvez appeler IList.Add()et recevoir une exception d'exécution:

Unhandled Exception: System.NotSupportedException: Collection was of a fixed size.

Par exemple, considérez le code suivant:

private void test(IList<int> list)
{
    list.Add(1);
}

Si vous appelez cela comme suit, vous obtiendrez une exception d'exécution:

int[] array = new int[0];
test(array);

Cela se produit parce que l'utilisation de tableaux simples avec IList<T>viole le principe de substitution de Liskov.

Pour cette raison, si vous appelez, IList<T>.Add()vous voudrez peut-être envisager d'exiger un fichier au List<T>lieu d'un IList<T>.

Matthew Watson
la source
C'est trivialement vrai pour chaque interface. Si vous voulez donner suite à votre argument, vous pourriez dire de ne jamais utiliser d'interface du tout, car une implémentation de celle-ci pourrait en déclencher. Si vous, d'autre part, considérer la suggestion donnée par OP à préférer List<T>plus IList<T>, vous devriez aussi être conscient des raisons pour lesquelles IList<T>est recommandé. (Par exemple, blogs.msdn.microsoft.com/kcwalina/2005/09/26/… )
Micha Wiedenmann
3
@MichaWiedenmann Ma réponse ici est spécifique au moment où vous appelez IList<T>.Add(). Je ne dis pas que vous ne devriez pas utiliser IList<T>- je signale simplement un piège possible. (J'ai tendance à utiliser IEnumerable<T>ou IReadOnlyList<T>ou IReadOnlyCollection<T>de préférence IList<T>si je peux.)
Matthew Watson
24

Je serais d'accord avec les conseils de Lee pour prendre des paramètres, mais ne pas revenir.

Si vous spécifiez vos méthodes pour renvoyer une interface, cela signifie que vous êtes libre de modifier l'implémentation exacte ultérieurement sans que la méthode consommatrice ne le sache jamais. Je pensais que je n'aurais jamais besoin de changer d'une liste <T> mais j'ai dû changer plus tard pour utiliser une bibliothèque de listes personnalisée pour les fonctionnalités supplémentaires qu'elle fournissait. Parce que je n'avais renvoyé qu'un IList <T>, aucune des personnes qui utilisaient la bibliothèque n'a dû changer son code.

Bien sûr, cela ne doit s'appliquer qu'aux méthodes visibles de l'extérieur (c'est-à-dire aux méthodes publiques). J'utilise personnellement des interfaces même dans le code interne, mais comme vous pouvez changer tout le code vous-même si vous apportez des modifications de rupture, ce n'est pas strictement nécessaire.

ICR
la source
22

IEnumerable
Vous devriez essayer d'utiliser le type le moins spécifique qui correspond à votre objectif.
IEnumerableest moins spécifique que IList.
Vous utilisez IEnumerablelorsque vous souhaitez parcourir les éléments d'une collection.

IList
IList met en œuvre IEnumerable.
Vous devez l'utiliser IListlorsque vous avez besoin d'accéder par index à votre collection, ajouter et supprimer des éléments, etc ...

Liste des
List outils IList.

Rajesh
la source
3
Excellente réponse claire, que j'ai marquée comme utile. Cependant, j'ajouterais que pour la plupart des développeurs, la plupart du temps, la petite différence de taille et de performances du programme ne vaut pas la peine de s'inquiéter: en cas de doute, utilisez simplement une liste.
Graham Laight
9

Il est toujours préférable d'utiliser le type de base le plus bas possible. Cela donne à l'implémenteur de votre interface, ou au consommateur de votre méthode, la possibilité d'utiliser ce qu'il veut dans les coulisses.

Pour les collections, vous devez viser à utiliser IEnumerable lorsque cela est possible. Cela donne le plus de flexibilité mais n'est pas toujours adapté.

tgmdbm
la source
1
Il est toujours préférable d' accepter le type de base le plus bas possible. Le retour est une autre histoire. Choisissez les options susceptibles d'être utiles. Vous pensez donc que votre client voudra peut-être utiliser un accès indexé? Empêchez-les de ToList()retourner votre produit IEnumerable<T>qui était déjà une liste et renvoyez-en un à la IList<T>place. Désormais, les clients peuvent bénéficier de ce que vous pouvez offrir sans effort.
Timo
5

Si vous travaillez dans une seule méthode (ou même dans une seule classe ou un seul assembly dans certains cas) et que personne à l'extérieur ne verra ce que vous faites, utilisez la plénitude d'une liste. Mais si vous interagissez avec du code extérieur, comme lorsque vous renvoyez une liste à partir d'une méthode, vous ne voulez que déclarer l'interface sans nécessairement vous attacher à une implémentation spécifique, surtout si vous n'avez aucun contrôle sur qui compile contre votre code après. Si vous avez commencé avec un type concret et que vous avez décidé de changer pour un autre, même s'il utilise la même interface, vous allez casser le code de quelqu'un d'autre à moins que vous n'ayez commencé avec une interface ou un type de base abstrait.

Mark Cidade
la source
4

Je ne pense pas qu'il y ait de règles strictes et rapides pour ce type de chose, mais j'utilise généralement la directive la plus légère possible jusqu'à ce que cela soit absolument nécessaire.

Par exemple, disons que vous avez une Personclasse et une Groupclasse. Une Groupinstance a de nombreuses personnes, donc une liste ici aurait du sens. Quand je déclare l'objet de liste dans Groupj'utiliserai un IList<Person>et l'instancierai en tant que List.

public class Group {
  private IList<Person> people;

  public Group() {
    this.people = new List<Person>();
  }
}

Et, si vous n'avez même pas besoin de tout, IListvous pouvez toujours l'utiliser IEnumerableaussi. Avec les compilateurs et les processeurs modernes, je ne pense pas qu'il y ait vraiment de différence de vitesse, donc c'est plus juste une question de style.

swilliams
la source
3
pourquoi ne pas en faire une simple liste en premier lieu? Je ne comprends toujours pas pourquoi vous obtenez un bonus en en faisant un IList, puis dans le constructeur, vous en faites une liste <>
chobo2
Je suis d'accord, si vous créez explicitement un objet List <T>, vous perdez l'avantage de l'interface?
The_Butcher
4

Il est le plus souvent préférable d'utiliser le type utilisable le plus général, dans ce cas, IList ou mieux encore l'interface IEnumerable, afin de pouvoir changer l'implémentation plus facilement ultérieurement.

Cependant, dans .NET 2.0, il y a une chose ennuyeuse - IList n'a pas de méthode Sort () . Vous pouvez utiliser à la place un adaptateur fourni:

ArrayList.Adapter(list).Sort()
petr k.
la source
2

Vous ne devez utiliser l'interface que si vous en avez besoin, par exemple, si votre liste est transtypée en une implémentation IList autre que List. Cela est vrai lorsque, par exemple, vous utilisez NHibernate, qui convertit les ILists dans un objet sac NHibernate lors de la récupération de données.

Si List est la seule implémentation que vous utiliserez pour une certaine collection, n'hésitez pas à la déclarer comme une implémentation concrète de List.

Jon Limjap
la source
1

Dans les situations que je rencontre habituellement, j'utilise rarement IList directement.

Habituellement, je l'utilise juste comme argument d'une méthode

void ProcessArrayData(IList almostAnyTypeOfArray)
{
    // Do some stuff with the IList array
}

Cela me permettra de faire un traitement générique sur presque tous les tableaux du framework .NET, à moins qu'il n'utilise IEnumerable et non IList, ce qui arrive parfois.

Cela dépend vraiment du type de fonctionnalité dont vous avez besoin. Je suggère d'utiliser la classe List dans la plupart des cas. IList est le meilleur lorsque vous avez besoin de créer un tableau personnalisé qui pourrait avoir des règles très spécifiques que vous souhaitez encapsuler dans une collection afin de ne pas vous répéter, mais que vous voulez quand même que .NET le reconnaisse comme une liste.

Dan Herbert
la source
1

L'objet AList vous permet de créer une liste, d'y ajouter des éléments, de la supprimer, de la mettre à jour, de l'indexer, etc. La liste est utilisée chaque fois que vous voulez juste une liste générique dans laquelle vous spécifiez le type d'objet et c'est tout.

IList, quant à lui, est une interface. Fondamentalement, si vous souhaitez créer votre propre type de liste, par exemple une classe de liste appelée BookList, vous pouvez utiliser l'interface pour vous donner les méthodes de base et la structure de votre nouvelle classe. IList est utilisé lorsque vous souhaitez créer votre propre sous-classe spéciale qui implémente List.

Une autre différence est: IList est une interface et ne peut pas être instancié. La liste est une classe et peut être instanciée. Ça veut dire:

IList<string> MyList = new IList<string>();

List<string> MyList = new List<string>
Javid
la source