wake-up-neo.com

Liste <T> oder IList <T>

Kann mir jemand erklären, warum ich IList über Liste in C # verwenden möchte?

Zugehörige Frage: Warum wird es als schlecht angesehen, List<T> auszustellen?

457
Peanut

Wenn Sie Ihre Klasse über eine Bibliothek verfügbar machen, die von anderen verwendet wird, möchten Sie sie normalerweise über Schnittstellen und nicht über konkrete Implementierungen verfügbar machen. Dies ist hilfreich, wenn Sie die Implementierung Ihrer Klasse später ändern möchten, um eine andere konkrete Klasse zu verwenden. In diesem Fall müssen die Benutzer Ihrer Bibliothek ihren Code nicht aktualisieren, da sich die Benutzeroberfläche nicht ändert.

Wenn Sie es nur intern verwenden, ist es Ihnen möglicherweise nicht so wichtig, und List<T> ist möglicherweise in Ordnung.

412
tvanfosson

Die weniger beliebte Antwort ist, dass Programmierer so tun, als würden sie so tun, als ob ihre Software auf der ganzen Welt wiederverwendet wird. Wenn tatsächlich die Mehrheit der Projekte von einer kleinen Anzahl von Leuten gepflegt wird, und wenn auch nette Interface-bezogene Soundbites sind, täuschen Sie dich selbst.

Architektur-Astronauten . Die Wahrscheinlichkeit, dass Sie jemals Ihre eigene IList schreiben, die den bereits im .NET-Framework vorhandenen etwas hinzufügt, ist so gering, dass sie theoretisch nur für "Best Practices" reserviert ist.

Software astronauts

Wenn Sie gefragt werden, was Sie in einem Interview gebrauchen, sagen Sie natürlich, IList, lächeln, und beide freuen sich, dass sie so klug sind. Oder für eine öffentliche API, IList. Hoffentlich verstehst du meinen Punkt.

289
Arec Barrwin

Schnittstelle ist ein Versprechen (oder ein Vertrag).

Da ist es immer bei den Versprechen - kleiner, je besser .

183
Rinat Abdullin

Einige Leute sagen, "verwenden Sie immer IList<T> anstelle von List<T>".
Sie möchten, dass Sie Ihre Methodensignaturen von void Foo(List<T> input) in void Foo(IList<T> input) ändern.

Diese Leute haben Unrecht.

Es ist nuancierter als das. Wenn Sie einen IList<T> als Teil der öffentlichen Schnittstelle an Ihre Bibliothek zurückgeben, überlassen Sie sich interessante Optionen, um in Zukunft möglicherweise eine benutzerdefinierte Liste zu erstellen. Möglicherweise brauchen Sie diese Option nie, aber es ist ein Argument. Ich denke, es ist das gesamte Argument für die Rückgabe der Schnittstelle anstelle des konkreten Typs. Es ist erwähnenswert, aber in diesem Fall hat es einen gravierenden Fehler.

Als geringfügiges Gegenargument kann es vorkommen, dass jeder Anrufer sowieso einen List<T> benötigt, und der aufrufende Code ist mit .ToList() übersät.

Aber viel wichtiger: Wenn Sie eine IList als Parameter akzeptieren, sollten Sie vorsichtig sein, da sich IList<T> und List<T> nicht auf dieselbe Weise verhalten. Trotz der Ähnlichkeit im Namen und trotz der Freigabe eines Interface machen sie not nicht dasselbe Vertrag.

Angenommen, Sie haben diese Methode:

public Foo(List<int> a)
{
    a.Add(someNumber);
}

Ein hilfreicher Kollege "überarbeitet" die Methode, um IList<int> zu akzeptieren.

Ihr Code ist jetzt fehlerhaft, weil int[]IList<int> implementiert, jedoch eine feste Größe hat. Der Vertrag für ICollection<T> (die Basis von IList<T>) erfordert, dass der Code, mit dem er verwendet wird, das Flag IsReadOnly überprüft, bevor versucht wird, Elemente zur Auflistung hinzuzufügen oder daraus zu entfernen. Der Vertrag für List<T> besteht nicht.

Das Liskov-Substitutionsprinzip (vereinfacht) legt fest, dass ein abgeleiteter Typ anstelle eines Basistyps verwendet werden kann, ohne zusätzliche Voraussetzungen oder Nachbedingungen.

Dies fühlt sich an, als ob es das Liskov-Substitutionsprinzip verletzt.

 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

Aber es tut nicht Die Antwort darauf ist, dass das Beispiel IList <T>/ICollection <T> falsch verwendet hat. Wenn Sie eine ICollection <T> verwenden, müssen Sie das IsReadOnly-Flag überprüfen. 

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?
}

Wenn Ihnen jemand ein Array oder eine Liste übergibt, funktioniert Ihr Code gut, wenn Sie jedes Mal die Flagge markieren und einen Fallback haben ... Aber wirklich; Wer macht das? Wissen Sie nicht im Voraus, ob Ihre Methode eine Liste benötigt, die zusätzliche Mitglieder aufnehmen kann? spezifizieren Sie das nicht in der Methodensignatur? Was genau würden Sie tun, wenn Ihnen eine schreibgeschützte Liste wie int[] übergeben wurde?

Sie können einen List<T> in einen Code einsetzen, der IList<T>/ICollection<T>richtig verwendet. Sie können nicht garantieren, dass Sie einen IList<T>/ICollection<T> in einen Code einsetzen können, der List<T> verwendet.

Es gibt einen Appell an das Prinzip der Einzelverantwortung/Schnittstellentrennung in vielen Argumenten, Abstraktionen anstelle von konkreten Typen zu verwenden - abhängig von der engsten möglichen Schnittstelle. In den meisten Fällen, wenn Sie einen List<T> verwenden und denken, Sie könnten stattdessen eine engere Schnittstelle verwenden - warum nicht IEnumerable<T>? Dies ist häufig eine bessere Lösung, wenn Sie keine Elemente hinzufügen müssen. Wenn Sie der Sammlung hinzufügen möchten, verwenden Sie den konkreten Typ List<T>.

Für mich ist IList<T> (und ICollection<T>) der schlechteste Teil des .NET-Frameworks. IsReadOnly verstößt gegen das Prinzip der geringsten Überraschung. Eine Klasse wie Array, die niemals das Hinzufügen, Einfügen oder Entfernen von Elementen zulässt, sollte keine Schnittstelle mit den Methoden Add, Insert und Remove implementieren. (Siehe auch https://softwareengineering.stackexchange.com/questions/306105/implementing-an-interface-hen-you-dont-need-one-of-the-properties )

Passt IList<T> zu Ihrer Organisation? Wenn ein Kollege Sie auffordert, eine Methodensignatur so zu ändern, dass IList<T> anstelle von List<T> verwendet wird, fragen Sie sie, wie sie einem IList<T> ein Element hinzufügen. Wenn sie IsReadOnly nicht kennen (und die meisten Leute nicht), verwenden Sie IList<T> nicht. Je.


Beachten Sie, dass das IsReadOnly-Flag von ICollection <T> stammt und angibt, ob Elemente zur Auflistung hinzugefügt oder daraus entfernt werden können. Aber nur um wirklich Dinge zu verwirren, gibt es nicht an, ob sie ersetzt werden können, was bei Arrays (die IsReadOnlys == true zurückgeben) sein kann.

Weitere Informationen zu IsReadOnly finden Sie unter msdn-Definition von ICollection <T> .IsReadOnly

57
perfectionist

List<T> ist eine spezifische Implementierung von IList<T>, einem Container, der wie ein lineares Array T[] mit einem ganzzahligen Index angesprochen werden kann. Wenn Sie als Argumenttyp der Methode IList<T> angeben, geben Sie nur an, dass Sie bestimmte Funktionen des Containers benötigen. 

Beispielsweise erzwingt die Schnittstellenspezifikation keine bestimmte zu verwendende Datenstruktur. Die Implementierung von List<T> erfolgt mit der gleichen Leistung für den Zugriff, das Löschen und das Hinzufügen von Elementen als lineares Array. Sie können sich jedoch eine Implementierung vorstellen, die stattdessen durch eine verknüpfte Liste unterstützt wird, für die das Hinzufügen von Elementen am Ende billiger ist (konstante Zeit), aber der Direktzugriff viel teurer ist. (Beachten Sie, dass .NET LinkedList<T> nicht IList<T> implementiert.)

In diesem Beispiel erfahren Sie außerdem, dass in bestimmten Situationen Situationen auftreten können, in denen Sie die Implementierung und nicht die Schnittstelle in der Argumentliste angeben müssen: In diesem Beispiel, wenn Sie ein bestimmtes Zugriffsleistungsmerkmal benötigen. Dies ist normalerweise für eine bestimmte Implementierung eines Containers gewährleistet (List<T> Dokumentation: "Sie implementiert die generische IList<T>-Schnittstelle mit einem Array, dessen Größe bei Bedarf dynamisch erhöht wird.").

Darüber hinaus möchten Sie möglicherweise die geringste erforderliche Funktionalität bereitstellen. Zum Beispiel. Wenn Sie den Inhalt der Liste nicht ändern müssen, sollten Sie wahrscheinlich IEnumerable<T> verwenden, der IList<T> erweitert wird.

37
ILoveFortran

Ich würde die Frage ein wenig umdrehen, anstatt zu begründen, warum Sie die Schnittstelle für die konkrete Implementierung verwenden sollten. Wenn Sie es nicht rechtfertigen können, verwenden Sie die Schnittstelle.

27
Patrik Hägne

IList <T> ist eine Schnittstelle, mit der Sie eine andere Klasse erben und dennoch IList <T> implementieren können, während List <T> dies verhindert.

Wenn beispielsweise eine Klasse A vorhanden ist und Ihre Klasse B sie erbt, können Sie die Liste <T> nicht verwenden

class A : B, IList<T> { ... }
18
Diadistis

Ein Prinzip von TDD und OOP ist im Allgemeinen das Programmieren an einer Schnittstelle und keine Implementierung.

In diesem speziellen Fall ist es in der Regel nicht von Bedeutung, da Sie im Wesentlichen von einem Sprachkonstrukt sprechen, nicht jedoch von einem benutzerdefinierten, sondern sagen Sie zum Beispiel, dass Sie fanden, dass List etwas nicht unterstützte, das Sie benötigten. Wenn Sie IList im Rest der App verwendet hätten, könnten Sie List mit Ihrer eigenen benutzerdefinierten Klasse erweitern und diese auch ohne Refactoring weitergeben.

Die Kosten dafür sind minimal, warum sollten Sie sich später die Kopfschmerzen sparen? Darum geht es beim Schnittstellenprinzip.

13
annakata
public void Foo(IList<Bar> list)
{
     // Do Something with the list here.
}

In diesem Fall können Sie jede Klasse übergeben, die die IList <Bar> -Schnittstelle implementiert. Wenn Sie stattdessen List <Bar> verwenden, kann nur eine List <Bar> -Instanz übergeben werden.

Der IList-Weg ist locker gekoppelt als der Listen-Weg.

12
smack0007

Es wird unterdrückt, dass keine dieser Fragen (oder Antworten) von List gegen IList den Unterscheidungsunterschied erwähnt. (Deshalb habe ich diese Frage zu SO gesucht!)

Hier sind also die in List enthaltenen Methoden, die zumindest in .NET 4.5 (circa 2015) nicht in IList zu finden sind.

  • AddRange
  • AsReadOnly
  • Binäre Suche
  • Kapazität
  • Konvertieren alle
  • Existiert
  • Finden
  • Finde alle
  • FindIndex
  • FindLast
  • FindLastIndex
  • Für jeden
  • GetRange
  • InsertRange
  • LastIndexOf
  • Alles entfernen
  • RemoveRange
  • Umkehren
  • Sortieren
  • ToArray
  • TrimExcess
  • TrueForAll
11
JasonS

Der wichtigste Fall für die Verwendung von Schnittstellen über Implementierungen sind die Parameter für Ihre API. Wenn Ihre API einen List-Parameter verwendet, muss jeder, der sie verwendet, List verwenden. Wenn der Parametertyp IList ist, hat der Aufrufer viel mehr Freiheit und kann Klassen verwenden, von denen Sie noch nie gehört haben, die möglicherweise nicht vorhanden waren, als Ihr Code geschrieben wurde.

10

Was ist, wenn .NET 5.0 System.Collections.Generic.List<T> durch System.Collection.Generics.LinearList<T> ersetzt. .NET besitzt immer den Namen List<T>, garantiert jedoch, dass IList<T> ein Vertrag ist. IMHO sollten wir (zumindest ich) nicht den Namen von jemandem verwenden (obwohl es in diesem Fall .NET ist) und später in Schwierigkeiten geraten.

Im Falle der Verwendung von IList<T> hat der Aufrufer immer die Garantie, dass alles funktioniert, und der Implementierer kann die zugrunde liegende Sammlung in eine alternative konkrete Implementierung von IList ändern.

7
Soundararajan

Alle Konzepte sind im Wesentlichen in den meisten der obigen Antworten aufgeführt, warum Schnittstellen für konkrete Implementierungen verwendet werden.

IList<T> defines those methods (not including extension methods)

IList<T>MSDN-Link

  1. Hinzufügen
  2. Klar
  3. Enthält
  4. Kopieren nach
  5. GetEnumerator
  6. Index von
  7. Einfügen
  8. Löschen
  9. RemoveAt

List<T> implementiert diese neun Methoden (ohne Erweiterungsmethoden). Darüber hinaus gibt es ungefähr 41 öffentliche Methoden, die bei Ihrer Überlegung die Frage nach der in Ihrer Anwendung verwendeten Methode wiegen.

List<T>MSDN-Link

6
yantaq

Die Definition einer IList oder einer ICollection würde sich für andere Implementierungen Ihrer Schnittstellen öffnen.

Möglicherweise möchten Sie ein IOrderRepository haben, das eine Sammlung von Bestellungen in einer IList oder einer ICollection definiert. Sie könnten dann verschiedene Arten von Implementierungen haben, um eine Liste von Aufträgen bereitzustellen, sofern diese den von Ihrer IList oder ICollection definierten "Regeln" entsprechen.

3
Peter Lindholm

IList <> ist fast immer vorzuziehen, wie in den Anweisungen des anderen Posters beschrieben. Beachten Sie jedoch in .NET 3.5 sp 1 ist ein Fehler aufgetreten. Wenn Sie eine IList <> durch mehrere Serialisierungs-/Deserialisierungszyklen mit dem WCF DataContractSerializer ausführen.

Es gibt jetzt ein SP, um diesen Fehler zu beheben: KB 971030

2
StuartLC

Die Schnittstelle stellt sicher, dass Sie mindestens die Methoden erhalten, die Sie erwarten; Kenntnis der Definition der Schnittstelle, dh. alle abstrakten Methoden, die von jeder Klasse implementiert werden können, die das Interface erbt. Wenn also jemand eine große Klasse mit mehreren Methoden selbst erstellt, abgesehen von den Methoden, die er für einige Zusatzfunktionen von der Schnittstelle geerbt hat, und diese für Sie nicht von Nutzen sind, ist es besser, einen Verweis auf eine Unterklasse (in diesem Fall die interface) und ordnen Sie ihm das konkrete Klassenobjekt zu.

ein weiterer Vorteil ist, dass Ihr Code vor Änderungen an einer konkreten Klasse sicher ist, da Sie nur einige der Methoden einer konkreten Klasse abonnieren. Diese Methoden werden dort verfügbar sein, solange die konkrete Klasse von der Schnittstelle erbt, die Sie verwenden mit. Ihre Sicherheit für Sie und Ihre Freiheit für den Programmierer, der eine konkrete Implementierung schreibt, um seine Klasse zu ändern oder um weitere Funktionen zu erweitern.

2
vishy

Sie können dieses Argument aus verschiedenen Blickwinkeln betrachten, einschließlich eines rein OO - Ansatzes, der besagt, dass die Programmierung gegen eine Schnittstelle und keine Implementierung erfolgen soll. Mit diesem Gedanken folgt die Verwendung von IList dem gleichen Prinzip wie das Weitergeben und Verwenden von Schnittstellen, die Sie von Grund auf definieren. Ich glaube auch an die Skalierbarkeits- und Flexibilitätsfaktoren, die ein Interface generell bietet. Wenn eine klassenimplementierende IList <T> erweitert oder geändert werden muss, muss der verbrauchende Code nicht geändert werden. Sie weiß, worauf sich der IList-Schnittstellenvertrag bezieht. Die Verwendung einer konkreten Implementierung und List <T> für eine Klasse, die geändert wird, kann jedoch dazu führen, dass der aufrufende Code ebenfalls geändert werden muss. Dies liegt daran, dass eine Klasse, die sich an IList <T> hält, ein bestimmtes Verhalten garantiert, das nicht durch einen konkreten Typ mit Liste <T> garantiert wird. 

Wenn Sie beispielsweise die Standardimplementierung von List <T> für eine Klasse ändern möchten. Durch Implementieren von IList <T> beispielsweise .Add, .Remove oder eine andere IList-Methode geben dem Entwickler eine Menge von Flexibilität und Leistung, ansonsten durch Liste <T> vordefiniert

1
atconway

In der Regel empfiehlt es sich, IList in Ihrer öffentlich zugänglichen API zu verwenden (sofern zutreffend und Listensemik erforderlich). Dadurch können Sie zu einer anderen Implementierung von IList wechseln, ohne den Code zu beschädigen, der Ihre Klasse verwendet.

Die Klassennamenliste kann im nächsten .net-Framework geändert werden, aber die Schnittstelle wird sich niemals ändern, da die Schnittstelle Vertrag ist.

Wenn Ihre API nur in foreach-Schleifen usw. verwendet wird, sollten Sie stattdessen IEnumerable verfügbar machen.

0
lazydeveloper