Kann mir jemand erklären, warum ich IList over List in C # verwenden möchte?
Verwandte Frage: Warum wird es als schlecht angesehen, zu belichtenList<T>
Kann mir jemand erklären, warum ich IList over List in C # verwenden möchte?
Verwandte Frage: Warum wird es als schlecht angesehen, zu belichtenList<T>
Antworten:
Wenn Sie Ihre Klasse über eine Bibliothek verfügbar machen, die andere verwenden, möchten Sie sie im Allgemeinen über Schnittstellen und nicht über konkrete Implementierungen verfügbar machen. Dies ist hilfreich, wenn Sie die Implementierung Ihrer Klasse später ändern, 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 die Verwendung ist
List<T>
möglicherweise in Ordnung.quelle
Die weniger beliebte Antwort ist, dass Programmierer gerne so tun, als würden ihre Software auf der ganzen Welt wiederverwendet, wenn tatsächlich die meisten Projekte von einer kleinen Anzahl von Leuten verwaltet werden und Sie sich täuschen, wie nett die Soundbites im Zusammenhang mit der Benutzeroberfläche auch sein mögen du selber.
Architektur Astronauten . Die Chancen, dass Sie jemals eine eigene IList schreiben, die etwas zu den bereits im .NET Framework vorhandenen hinzufügt, sind so gering, dass es sich um theoretische Jelly Tots handelt, die "Best Practices" vorbehalten sind.
Wenn Sie gefragt werden, was Sie in einem Interview verwenden, sagen Sie natürlich IList, lächeln und beide freuen sich, dass Sie so schlau sind. Oder für eine öffentlich zugängliche API, IList. Hoffentlich verstehst du meinen Standpunkt.
quelle
IEnumerable<string>
. Innerhalb der Funktion kann ich eineList<string>
für einen internen Sicherungsspeicher verwenden, um meine Sammlung zu generieren, aber ich möchte nur, dass Aufrufer deren Inhalt auflisten, nicht hinzufügen oder entfernen. Wenn Sie eine Schnittstelle als Parameter akzeptieren, wird eine ähnliche Meldung angezeigt: "Ich benötige eine Sammlung von Zeichenfolgen, aber keine Sorge, ich werde sie nicht ändern."Schnittstelle ist ein Versprechen (oder ein Vertrag).
Wie immer bei den Versprechungen - kleiner desto besser .
quelle
IList
ist das Versprechen. Welches ist das kleinere und bessere Versprechen hier? Das ist nicht logisch.List<T>
, weilAddRange
auf der Schnittstelle nicht definiert ist.IList<T>
macht Versprechungen, die es nicht halten kann. Zum Beispiel gibt es eineAdd
Methode, die ausgelöst werden kann, wennIList<T>
sie schreibgeschützt ist.List<T>
andererseits ist immer beschreibbar und hält somit immer seine Versprechen. Auch seit .NET 4.6 haben wir jetztIReadOnlyCollection<T>
undIReadOnlyList<T>
die passen fast immer besser alsIList<T>
. Und sie sind kovariant. Vermeiden SieIList<T>
!IList<T>
. Jemand könnte genauso gut implementierenIReadOnlyList<T>
und jede Methode auch eine Ausnahme auslösen lassen. Es ist das Gegenteil von Ententypisierung - Sie nennen es eine Ente, aber es kann nicht wie eine quaken. Dies ist jedoch Teil des Vertrags, auch wenn der Compiler ihn nicht durchsetzen kann. Ich stimme jedoch zu 100% zu, dassIReadOnlyList<T>
oderIReadOnlyCollection<T>
sollte nur für den Lesezugriff verwendet werden.Einige Leute sagen "immer verwenden
IList<T>
stattList<T>
".Sie möchten, dass Sie Ihre Methodensignaturen von
void Foo(List<T> input)
bis ändernvoid Foo(IList<T> input)
.Diese Leute sind falsch.
Es ist nuancierter als das. Wenn Sie eine
IList<T>
als Teil der öffentlichen Schnittstelle an Ihre Bibliothek zurückgeben, lassen Sie sich interessante Optionen, um möglicherweise in Zukunft eine benutzerdefinierte Liste zu erstellen. Möglicherweise benötigen 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 schwerwiegenden Fehler.Als kleines Gegenargument stellen Sie möglicherweise fest, dass jeder einzelne Anrufer
List<T>
ohnehin einen benötigt , und der Anrufcode ist übersät.ToList()
Aber viel wichtiger ist, wenn Sie eine IList als Parameter akzeptieren, sollten Sie besser vorsichtig sein, weil
IList<T>
undList<T>
sich nicht gleich verhalten. Trotz der Ähnlichkeit im Namen und trotz der gemeinsamen Nutzung einer Schnittstelle legen sie nicht denselben Vertrag offen .Angenommen, Sie haben diese Methode:
Ein hilfreicher Kollege "refaktoriert" die zu akzeptierende Methode
IList<int>
.Ihr Code ist jetzt defekt, weil
int[]
implementiertIList<int>
, hat aber eine feste Größe. Der Vertrag fürICollection<T>
(die Basis vonIList<T>
) erfordert den Code, mit dem dasIsReadOnly
Flag überprüft wird, bevor versucht wird, Elemente zur Sammlung hinzuzufügen oder daraus zu entfernen. Der Vertrag fürList<T>
nicht.Das Liskov-Substitutionsprinzip (vereinfacht) besagt, dass ein abgeleiteter Typ anstelle eines Basistyps ohne zusätzliche Vor- oder Nachbedingungen verwendet werden kann.
Dies scheint das Liskov-Substitutionsprinzip zu brechen.
Aber das tut es nicht. Die Antwort darauf ist, dass im Beispiel IList <T> / ICollection <T> falsch verwendet wurde. Wenn Sie eine ICollection <T> verwenden, müssen Sie das IsReadOnly-Flag überprüfen.
Wenn Ihnen jemand ein Array oder eine Liste übergibt, funktioniert Ihr Code einwandfrei, wenn Sie jedes Mal das Flag überprüfen 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? Geben Sie das nicht in der Methodensignatur an? Was genau wollten Sie tun, wenn Sie eine schreibgeschützte Liste erhalten haben
int[]
?Sie können einen
List<T>
In-Code ersetzen , derIList<T>
/ICollection<T>
korrekt verwendet . Sie können nicht garantieren, dass Sie einenIList<T>
/ICollection<T>
in-Code ersetzen können , der verwendet wirdList<T>
.In vielen Argumenten zur Verwendung von Abstraktionen anstelle konkreter Typen wird auf das Prinzip der Einzelverantwortung / Schnittstellentrennung verwiesen - abhängig von der engstmöglichen Schnittstelle. In den meisten Fällen, wenn Sie a verwenden
List<T>
und glauben, Sie könnten stattdessen eine schmalere Oberfläche verwenden - warum nichtIEnumerable<T>
? Dies passt oft besser, wenn Sie keine Elemente hinzufügen müssen. Wenn Sie der Sammlung hinzufügen müssen, verwenden Sie den konkreten TypList<T>
.Für mich
IList<T>
(undICollection<T>
) ist der schlechteste Teil des .NET Frameworks.IsReadOnly
verstößt gegen das Prinzip der geringsten Überraschung. Eine Klasse, die beispielsweiseArray
das Hinzufügen, Einfügen oder Entfernen von Elementen niemals zulässt, sollte keine Schnittstelle mit den Methoden Hinzufügen, Einfügen und Entfernen implementieren. (Siehe auch /software/306105/implementing-an-interface-when-you-dont-need-one-of-the-properties )Passt es
IList<T>
gut zu Ihrer Organisation? Wenn ein Kollege Sie stellt eine Methode zum Ändern der Signatur zu verwendenIList<T>
stattList<T>
, fragen sie , wie sie ein Element eine hinzufügen würdenIList<T>
. Wenn sie nichts wissenIsReadOnly
(und die meisten Leute nicht wissen ), dann verwenden Sie es nichtIList<T>
. Je.Beachten Sie, dass das IsReadOnly-Flag von ICollection <T> stammt und angibt, ob Elemente zur Sammlung hinzugefügt oder daraus entfernt werden können. Aber nur um die Dinge wirklich zu verwirren, gibt es keinen Hinweis darauf, ob sie ersetzt werden können, was im Fall von Arrays (die IsReadOnlys == true zurückgeben) sein kann.
Weitere Informationen zu IsReadOnly finden Sie unter msdn-Definition von ICollection <T> .IsReadOnly
quelle
IsReadOnly
es sichtrue
um eineIList
handelt! Vielleicht sollte diese Eigenschaft benannt werdenIsFixedSize
?Array
als getestetIList
, deshalb konnte ich die aktuellen Mitglieder ändern)IReadOnlyCollection<T>
undIReadOnlyList<T>
die passen fast immer besser alsIList<T>
, haben aber nicht die gefährliche faule Semantik vonIEnumerable<T>
. Und sie sind kovariant. Vermeiden SieIList<T>
!List<T>
ist eine spezifische Implementierung vonIList<T>
, bei der es sich um einen Container handelt, der auf die gleiche Weise wie ein lineares Array adressiert werden kannT[]
Verwendung eines ganzzahligen Index . Wenn SieIList<T>
als Typ des Argumentes der Methode 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 derselben Leistung für den Zugriff auf, das Löschen und 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), der Direktzugriff jedoch viel teurer ist. (Beachten Sie, dass .NETLinkedList<T>
dies nicht tut implementiertIList<T>
.)In diesem Beispiel erfahren Sie auch, dass es Situationen geben kann, 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 garantiert (
List<T>
Dokumentation: "Es implementiert dieIList<T>
generische Schnittstelle mithilfe eines Arrays, dessen Größe nach Bedarf dynamisch erhöht wird.").Darüber hinaus möchten Sie möglicherweise die geringste Funktionalität bereitstellen, die Sie benötigen. Zum Beispiel. Wenn Sie den Inhalt der Liste nicht ändern müssen, sollten Sie wahrscheinlich die Verwendung in Betracht ziehen
IEnumerable<T>
, dieIList<T>
erweitert wird.quelle
LinkedList<T>
gegen NehmenList<T>
gegenIList<T>
alle kommunizieren etwas von den Leistungsgarantien, die für den aufgerufenen Code erforderlich sind.Ich würde die Frage ein wenig umdrehen, anstatt zu rechtfertigen, warum Sie die Schnittstelle über der konkreten Implementierung verwenden sollten, versuchen Sie zu rechtfertigen, warum Sie die konkrete Implementierung anstelle der Schnittstelle verwenden würden. Wenn Sie es nicht rechtfertigen können, verwenden Sie die Schnittstelle.
quelle
IList <T> ist eine Schnittstelle, mit der Sie eine andere Klasse erben und dennoch IList <T> implementieren können, während das Erben von List <T> dies verhindert.
Wenn es beispielsweise eine Klasse A gibt und Ihre Klasse B diese erbt, können Sie List <T> nicht verwenden
quelle
Ein Prinzip von TDD und OOP ist im Allgemeinen das Programmieren auf eine Schnittstelle, keine Implementierung.
In diesem speziellen Fall spielt es keine Rolle, da es sich im Wesentlichen um ein Sprachkonstrukt handelt, nicht um ein benutzerdefiniertes, aber sagen Sie beispielsweise, dass Sie festgestellt haben, dass List etwas nicht unterstützt, das Sie benötigen. Wenn Sie IList im Rest der App verwendet haben, können Sie List um Ihre eigene benutzerdefinierte Klasse erweitern und diese dennoch ohne Refactoring weitergeben.
Die Kosten dafür sind minimal. Warum sparen Sie sich nicht später die Kopfschmerzen? Darum geht es beim Interface-Prinzip.
quelle
In diesem Fall können Sie jede Klasse übergeben, die die IList <Bar> -Schnittstelle implementiert. Wenn Sie stattdessen List <Bar> verwendet haben, konnte nur eine List <Bar> -Instanz übergeben werden.
Der IList <Bar> Weg ist lockerer gekoppelt als der List <Bar> Weg.
quelle
Unter der Annahme, dass keine dieser Fragen (oder Antworten) zwischen Liste und IList den Signaturunterschied erwähnt. (Deshalb habe ich auf SO nach dieser Frage gesucht!)
Hier sind die in List enthaltenen Methoden, die zumindest ab .NET 4.5 (ca. 2015) nicht in IList enthalten sind.
quelle
Reverse
undToArray
sind Erweiterungsmethoden für die IEnumerable <T> -Schnittstelle, von der IList <T> abgeleitet ist.List
s und nicht in anderen Implementierungen von sinnvoll sindIList
.Der wichtigste Fall für die Verwendung von Schnittstellen über Implementierungen sind die Parameter Ihrer API. Wenn Ihre API einen List-Parameter verwendet, muss jeder, der ihn 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 einmal vorhanden waren, als Ihr Code geschrieben wurde.
quelle
Was passiert , wenn .NET 5.0 ersetzt
System.Collections.Generic.List<T>
zuSystem.Collection.Generics.LinearList<T>
. .NET besitzt immer den Namen,List<T>
aber sie garantieren, dassIList<T>
es sich um einen Vertrag handelt. Meiner Meinung nach sollten wir (zumindest ich) nicht den Namen einer Person verwenden (obwohl es in diesem Fall .NET ist) und später in Schwierigkeiten geraten.Im Falle der Verwendung
IList<T>
wird dem Aufrufer immer garantiert, dass die Dinge funktionieren, und es steht dem Implementierer frei, die zugrunde liegende Sammlung in eine alternative konkrete Implementierung von zu ändernIList
quelle
Grundsätzlich werden in den meisten der obigen Antworten alle Konzepte angegeben, warum die Schnittstelle über konkreten Implementierungen verwendet wird.
IList<T>
MSDN-LinkList<T>
implementiert diese neun Methoden (ohne Erweiterungsmethoden), zusätzlich gibt es ungefähr 41 öffentliche Methoden, was Ihre Überlegung abwägt, welche in Ihrer Anwendung verwendet werden soll.List<T>
MSDN-Linkquelle
Sie würden, weil das Definieren einer IList oder einer ICollection sich für andere Implementierungen Ihrer Schnittstellen öffnen würde.
Möglicherweise möchten Sie ein IOrderRepository haben, das eine Sammlung von Aufträgen in einer IList oder einer ICollection definiert. Sie können dann verschiedene Arten von Implementierungen verwenden, um eine Liste von Aufträgen bereitzustellen, sofern diese den von Ihrer IList oder ICollection definierten "Regeln" entsprechen.
quelle
IList <> ist gemäß den Empfehlungen des anderen Posters fast immer vorzuziehen. Beachten Sie jedoch, dass in .NET 3.5 sp 1 ein Fehler vorliegt vorliegt, wenn eine IList <> mehr als einen Serialisierungs- / Deserialisierungszyklus mit dem WCF DataContractSerializer durchläuft.
Es gibt jetzt einen SP, um diesen Fehler zu beheben: KB 971030
quelle
Die Schnittstelle stellt sicher, dass Sie mindestens die Methoden erhalten, die Sie erwarten . Kenntnis der Definition der Schnittstelle, dh. Alle abstrakten Methoden, die von einer Klasse implementiert werden sollen, die die Schnittstelle erbt. Wenn also jemand eine große eigene Klasse mit mehreren Methoden außer den Methoden erstellt, die er von der Schnittstelle für einige zusätzliche Funktionen geerbt hat, und diese für Sie nicht von Nutzen sind, ist es besser, einen Verweis auf eine Unterklasse zu verwenden (in diesem Fall die Schnittstelle) und weisen ihm das konkrete Klassenobjekt zu.
Ein weiterer Vorteil besteht darin, dass Ihr Code vor Änderungen an der konkreten Klasse geschützt ist, da Sie nur einige der Methoden der konkreten Klasse abonnieren und diese Methoden vorhanden sein werden, solange die konkrete Klasse von der Schnittstelle erbt, die Sie sind mit. Dies ist die Sicherheit für Sie und die Freiheit für den Codierer, der eine konkrete Implementierung schreibt, um seine konkrete Klasse zu ändern oder um weitere Funktionen zu erweitern.
quelle
Sie können dieses Argument aus verschiedenen Blickwinkeln betrachten, einschließlich eines rein OO-Ansatzes, der besagt, dass das Programmieren gegen eine Schnittstelle keine Implementierung ist. Bei diesem Gedanken folgt die Verwendung von IList dem gleichen Prinzip wie die Weitergabe und Verwendung von Schnittstellen, die Sie von Grund auf neu definieren. Ich glaube auch an die Skalierbarkeits- und Flexibilitätsfaktoren, die eine Schnittstelle im Allgemeinen bietet. Wenn eine Klasse, die IList <T> implementiert, erweitert oder geändert werden muss, muss sich der konsumierende Code nicht ändern. es weiß, woran der IList Interface-Vertrag festhält. Die Verwendung einer konkreten Implementierung und List <T> für eine Klasse, die sich ändert, kann jedoch dazu führen, dass auch der aufrufende Code geändert werden muss. Dies liegt daran, dass eine Klasse, die IList <T> einhält, ein bestimmtes Verhalten garantiert, das von einem konkreten Typ mit List <T> nicht garantiert wird.
Die Möglichkeit, beispielsweise die Standardimplementierung von List <T> für eine Klasse zu implementieren, die IList <T> implementiert, z. B. .Add, .Remove oder eine andere IList-Methode, bietet dem Entwickler viel Flexibilität und Leistung, die ansonsten vordefiniert sind nach Liste <T>
quelle
In der Regel besteht ein guter Ansatz darin, IList in Ihrer öffentlich zugänglichen API zu verwenden (falls zutreffend, und Listensemantik ist erforderlich) und dann intern aufzulisten, um die API zu implementieren. Auf diese Weise 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 nie ändern, da die Schnittstelle vertraglich gebunden ist.
Beachten Sie, dass Sie möglicherweise nur IEnumerable verfügbar machen möchten, wenn Ihre API nur in foreach-Schleifen usw. verwendet werden soll.
quelle