Wann wird IList verwendet und wann wird List verwendet?

178

Ich weiß, dass IList die Schnittstelle und List der konkrete Typ ist, aber ich weiß immer noch nicht, wann ich jeden verwenden soll. Was ich jetzt mache, ist, wenn ich die Sortier- oder FindAll-Methoden nicht benötige, verwende ich die Schnittstelle. Habe ich recht? Gibt es eine bessere Möglichkeit zu entscheiden, wann die Schnittstelle oder der konkrete Typ verwendet werden soll?

Rismo
quelle
1
Wenn sich noch jemand wundert, finde ich hier die besten Antworten: stackoverflow.com/questions/400135/listt-or-ilistt
Crismogram

Antworten:

173

Ich befolge zwei Regeln:

  • Akzeptieren Sie den grundlegendsten Typ, der funktionieren wird
  • Geben Sie den umfangreichsten Typ zurück, den Ihr Benutzer benötigt

Wenn Sie also eine Funktion oder Methode schreiben, die eine Sammlung benötigt, schreiben Sie sie nicht in eine Liste, sondern in eine IList <T>, eine ICollection <T> oder eine IEnumerable <T>. Die generischen Schnittstellen funktionieren auch für heterogene Listen, da System.Object auch ein T sein kann. Dies erspart Ihnen Kopfschmerzen, wenn Sie sich später für die Verwendung eines Stacks oder einer anderen Datenstruktur entscheiden. Wenn alles, was Sie in der Funktion tun müssen, darin besteht, sie durchzuarbeiten, ist IEnumerable <T> wirklich alles, wonach Sie fragen sollten.

Wenn Sie andererseits ein Objekt aus einer Funktion zurückgeben, möchten Sie dem Benutzer die größtmögliche Anzahl von Operationen bieten, ohne dass er herumwirbeln muss. Wenn es sich in diesem Fall intern um eine Liste <T> handelt, geben Sie eine Kopie als Liste <T> zurück.

Lee
quelle
43
Sie sollten Eingabe- / Ausgabetypen nicht anders behandeln. Eingabe- und Ausgabetypen sollten beide der grundlegendste Typ (vorzugsweise Schnittstelle) sein, der die Kundenanforderungen unterstützt. Bei der Kapselung müssen die Kunden so wenig wie möglich über die Implementierung Ihrer Klasse informiert werden. Wenn Sie eine konkrete Liste zurückgeben, können Sie nicht zu einem anderen besseren Typ wechseln, ohne alle Ihre Clients zum erneuten Kompilieren / Aktualisieren zu zwingen.
Ash
11
Ich bin mit den 2 Regeln nicht einverstanden ... Ich würde bei der Rückgabe in diesem Fall IList (besser IEnumarable) den primitivsten Typ und die Spezialität verwenden, und Sie sollten in Ihrer Funktion mit List arbeiten. Wenn Sie dann "Hinzufügen" oder "Sortieren" benötigen, verwenden Sie "Sammlung", wenn Sie mehr benötigen, als "Liste". Meine harte Regel wäre also: STARTEN Sie immer mit IENumarable und wenn Sie mehr brauchen, verlängern Sie ...
ethem
2
Der Einfachheit halber haben die "zwei Regeln" einen Namen: das Robustheitsprinzip (auch bekannt als Postel-Gesetz) .
easoncxz
Unabhängig davon, auf welcher Seite der Debatte jemand darüber steht, ob der grundlegendste Typ oder der reichhaltigste Typ zurückgegeben werden soll, sollte berücksichtigt werden, dass der konsumierende Code bei der Rückgabe einer sehr vereinfachten Benutzeroberfläche häufig - wenn auch nicht immer - eine if...elseKette mit dem isSchlüsselwort verwenden kann, um die Zahl zu bestimmen einen viel reicheren Typ dafür herausholen und am Ende darauf werfen und ihn trotzdem benutzen. Es ist also nicht unbedingt garantiert, dass Sie etwas über eine grundlegende Benutzeroberfläche verbergen, anstatt es nur zu verdecken. Wenn Sie es jedoch schwieriger machen, kann der Verfasser des konsumierenden Codes auch zweimal darüber nachdenken, wie er verwendet wird.
Panzercrisis
6
Ich bin sehr stark anderer Meinung über Punkt 2, insbesondere wenn dies an einer Service- / API-Grenze liegt. Das Zurückgeben von modifizierbaren Sammlungen kann den Eindruck erwecken, dass die Sammlungen "live" sind und Methoden aufrufen, die über die Sammlung hinausgehen Add()und Remove()Auswirkungen haben können. Die Rückgabe einer schreibgeschützten Schnittstelle, wie sie IEnumerablehäufig für Datenabrufmethoden verwendet wird. Ihr Verbraucher kann es nach Bedarf in einen reichhaltigeren Typ projizieren.
STW
56

Von FxCop überprüfte Microsoft-Richtlinien raten von der Verwendung von List <T> in öffentlichen APIs ab - bevorzugen Sie IList <T>.

Übrigens deklariere ich jetzt fast immer eindimensionale Arrays als IList <T>, was bedeutet, dass ich die IList <T> .Count-Eigenschaft anstelle von Array.Length konsistent verwenden kann. Beispielsweise:

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
quelle
3
Diese Erklärung / dieses Beispiel gefällt mir am besten!
JonH
28

Es gibt eine wichtige Sache, die die Leute immer zu übersehen scheinen:

Sie können ein einfaches Array an etwas übergeben, das einen IList<T>Parameter akzeptiert , und dann können Sie IList.Add()eine Laufzeitausnahme aufrufen und erhalten:

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

Betrachten Sie beispielsweise den folgenden Code:

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

Wenn Sie dies wie folgt aufrufen, erhalten Sie eine Laufzeitausnahme:

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

Dies liegt daran, dass die Verwendung einfacher Arrays mit IList<T>gegen das Liskov-Substitutionsprinzip verstößt.

Wenn Sie anrufen, sollten IList<T>.Add()Sie aus diesem Grund in Betracht ziehen, ein List<T>anstelle eines zu verlangen IList<T>.

Matthew Watson
quelle
Dies gilt trivial für jede Schnittstelle. Wenn Sie mit Ihrem Argument weitermachen möchten, könnten Sie argumentieren, niemals eine Schnittstelle zu verwenden, da eine Implementierung davon möglicherweise zu Problemen führt. Wenn Sie, auf der anderen Seite, den Vorschlag von OP gegeben betrachten bevorzugen List<T>über IList<T>, sollten Sie auch die Gründe wissen, warum IList<T>empfohlen. (Zum Beispiel blogs.msdn.microsoft.com/kcwalina/2005/09/26/… )
Micha Wiedenmann
3
@MichaWiedenmann Meine Antwort hier ist spezifisch, wann Sie anrufen IList<T>.Add(). Ich sage nicht, dass Sie nicht verwenden sollten IList<T>- ich weise nur auf eine mögliche Falle hin. (Ich neige dazu, IEnumerable<T>oder IReadOnlyList<T>oder IReadOnlyCollection<T>lieber zu verwenden, IList<T>wenn ich kann.)
Matthew Watson
24

Ich würde Lees Rat zustimmen, Parameter zu übernehmen, aber nicht zurückzukehren.

Wenn Sie Ihre Methoden angeben, um eine Schnittstelle zurückzugeben, können Sie die genaue Implementierung später ändern, ohne dass die konsumierende Methode dies jemals weiß. Ich dachte, ich müsste nie von einer Liste <T> wechseln, sondern später, um eine benutzerdefinierte Listenbibliothek für die zusätzliche Funktionalität zu verwenden. Da ich nur eine IList <T> zurückgegeben hatte, musste keiner der Benutzer der Bibliothek seinen Code ändern.

Dies muss natürlich nur für Methoden gelten, die von außen sichtbar sind (dh öffentliche Methoden). Ich persönlich verwende Schnittstellen auch im internen Code, aber da Sie den gesamten Code selbst ändern können, wenn Sie wichtige Änderungen vornehmen, ist dies nicht unbedingt erforderlich.

ICR
quelle
21

IEnumerable
Sie sollten versuchen, den am wenigsten spezifischen Typ zu verwenden, der Ihrem Zweck entspricht.
IEnumerableist weniger spezifisch als IList.
Sie verwenden diese Option, IEnumerablewenn Sie die Elemente in einer Sammlung durchlaufen möchten.

IList
IList implementiert IEnumerable.
Sie sollten verwenden, IListwenn Sie Zugriff per Index auf Ihre Sammlung benötigen, Elemente hinzufügen und löschen usw.

Liste
List implementiert IList.

Rajesh
quelle
3
Ausgezeichnete, klare Antwort, die ich als hilfreich markiert habe. Ich möchte jedoch hinzufügen, dass für die meisten Entwickler der winzige Unterschied in Programmgröße und -leistung meistens keine Sorge wert ist: Verwenden Sie im Zweifelsfall einfach eine Liste.
Graham Laight
9

Es ist immer am besten, den niedrigstmöglichen Basistyp zu verwenden. Dies gibt dem Implementierer Ihrer Schnittstelle oder dem Konsumenten Ihrer Methode die Möglichkeit, hinter den Kulissen zu verwenden, was er möchte.

Für Sammlungen sollten Sie nach Möglichkeit IEnumerable verwenden. Dies bietet die größte Flexibilität, ist jedoch nicht immer geeignet.

tgmdbm
quelle
1
Es ist immer am besten , den niedrigstmöglichen Basistyp zu akzeptieren . Die Rückkehr ist eine andere Geschichte. Wählen Sie aus, welche Optionen wahrscheinlich nützlich sind. Sie denken also, Ihr Client möchte möglicherweise den indizierten Zugriff verwenden? Halten Sie sie davon ab, ToList()Ihre IEnumerable<T>Rücksendung zu senden , die bereits eine Liste war, und geben Sie IList<T>stattdessen eine zurück. Jetzt können Kunden mühelos von dem profitieren, was Sie bieten können.
Timo
5

Wenn Sie innerhalb einer einzelnen Methode arbeiten (oder in einigen Fällen sogar in einer einzelnen Klasse oder Assembly) und niemand außerhalb sehen wird, was Sie tun, verwenden Sie die Fülle einer Liste. Wenn Sie jedoch mit externem Code interagieren, z. B. wenn Sie eine Liste von einer Methode zurückgeben, möchten Sie die Schnittstelle nur deklarieren, ohne sich unbedingt an eine bestimmte Implementierung zu binden, insbesondere wenn Sie keine Kontrolle darüber haben, wer gegen Ihre kompiliert Code danach. Wenn Sie mit einem konkreten Typ begonnen haben und sich entschieden haben, zu einem anderen zu wechseln, auch wenn er dieselbe Schnittstelle verwendet, werden Sie den Code eines anderen Benutzers beschädigen, es sei denn, Sie haben mit einer Schnittstelle oder einem abstrakten Basistyp begonnen.

Mark Cidade
quelle
4

Ich glaube nicht, dass es feste Regeln für diese Art von Dingen gibt, aber ich halte mich normalerweise an die Richtlinie, den leichtesten Weg zu gehen, bis es absolut notwendig ist.

Angenommen, Sie haben eine PersonKlasse und eine GroupKlasse. Eine GroupInstanz hat viele Leute, daher wäre eine Liste hier sinnvoll. Wenn ich das Listenobjekt in deklariere, verwende Groupich ein IList<Person>und instanziiere es als List.

public class Group {
  private IList<Person> people;

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

Und wenn Sie nicht einmal alles brauchen IList, können Sie es auch immer verwenden IEnumerable. Bei modernen Compilern und Prozessoren gibt es meines Erachtens keinen Geschwindigkeitsunterschied, daher ist dies eher eine Frage des Stils.

Swilliams
quelle
3
Warum nicht gleich eine Liste daraus machen? Ich verstehe immer noch nicht, warum Sie einen Bonus erhalten, wenn Sie ihn zu einem IList machen. Dann machen Sie es im Konstruktor zu einer Liste <>
chobo2
Ich stimme zu, wenn Sie explizit ein List <T> -Objekt erstellen, verlieren Sie den Vorteil der Schnittstelle?
The_Butcher
4

Meistens ist es besser, den allgemeinsten verwendbaren Typ zu verwenden, in diesem Fall die IList oder noch besser die IEnumerable-Schnittstelle, damit Sie die Implementierung zu einem späteren Zeitpunkt bequem wechseln können.

In .NET 2.0 gibt es jedoch eine ärgerliche Sache: IList verfügt nicht über eine Sort () -Methode. Sie können stattdessen einen mitgelieferten Adapter verwenden:

ArrayList.Adapter(list).Sort()
petr k.
quelle
2

Sie sollten die Schnittstelle nur verwenden, wenn Sie sie benötigen, z. B. wenn Ihre Liste in eine andere IList-Implementierung als List umgewandelt wurde. Dies gilt beispielsweise, wenn Sie NHibernate verwenden, mit dem ILists beim Abrufen von Daten in ein NHibernate-Bag-Objekt umgewandelt werden.

Wenn List die einzige Implementierung ist, die Sie jemals für eine bestimmte Sammlung verwenden werden, können Sie sie als konkrete List-Implementierung deklarieren.

Jon Limjap
quelle
1

In Situationen, auf die ich normalerweise stoße, verwende ich IList selten direkt.

Normalerweise benutze ich es nur als Argument für eine Methode

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

Auf diese Weise kann ich eine generische Verarbeitung für fast jedes Array im .NET-Framework durchführen, es sei denn, es wird IEnumerable und nicht IList verwendet, was manchmal vorkommt.

Es kommt wirklich auf die Art von Funktionalität an, die Sie benötigen. Ich würde in den meisten Fällen die Verwendung der List-Klasse vorschlagen. IList eignet sich am besten, wenn Sie ein benutzerdefiniertes Array erstellen müssen, das einige sehr spezifische Regeln enthalten kann, die Sie in eine Sammlung einkapseln möchten, damit Sie sich nicht wiederholen, aber dennoch möchten, dass .NET es als Liste erkennt.

Dan Herbert
quelle
1

Mit dem AList-Objekt können Sie eine Liste erstellen, Dinge hinzufügen, entfernen, aktualisieren, indizieren usw. Die Liste wird immer dann verwendet, wenn Sie nur eine generische Liste möchten, in der Sie den Objekttyp angeben.

IList hingegen ist eine Schnittstelle. Wenn Sie einen eigenen Listentyp erstellen möchten, z. B. eine Listenklasse namens BookList, können Sie über die Schnittstelle grundlegende Methoden und Strukturen für Ihre neue Klasse festlegen. IList ist für den Fall gedacht, dass Sie eine eigene, spezielle Unterklasse erstellen möchten, die List implementiert.

Ein weiterer Unterschied ist: IList ist eine Schnittstelle und kann nicht instanziiert werden. List ist eine Klasse und kann instanziiert werden. Es bedeutet:

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

List<string> MyList = new List<string>
Javid
quelle