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.
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.
IEnumerable<string>
. Innerhalb der Funktion kann ich eine List<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 .
IList
ist das Versprechen. Welches ist das kleinere und bessere Versprechen hier? Das ist nicht logisch.
List<T>
, weil AddRange
auf der Schnittstelle nicht definiert ist.
IList<T>
macht Versprechungen, die es nicht halten kann. Zum Beispiel gibt es eine Add
Methode, die ausgelöst werden kann, wenn IList<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 jetzt IReadOnlyCollection<T>
und IReadOnlyList<T>
die passen fast immer besser als IList<T>
. Und sie sind kovariant. Vermeiden Sie IList<T>
!
IList<T>
. Jemand könnte genauso gut implementieren IReadOnlyList<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, dass IReadOnlyList<T>
oder IReadOnlyCollection<T>
sollte nur für den Lesezugriff verwendet werden.
Einige Leute sagen "immer verwenden IList<T>
statt List<T>
".
Sie möchten, dass Sie Ihre Methodensignaturen von void Foo(List<T> input)
bis ändern void 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>
und List<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:
public Foo(List<int> a)
{
a.Add(someNumber);
}
Ein hilfreicher Kollege "refaktoriert" die zu akzeptierende Methode IList<int>
.
Ihr Code ist jetzt defekt, weil int[]
implementiert IList<int>
, hat aber eine feste Größe. Der Vertrag für ICollection<T>
(die Basis von IList<T>
) erfordert den Code, mit dem das IsReadOnly
Flag überprüft wird, bevor versucht wird, Elemente zur Sammlung hinzuzufügen oder daraus zu entfernen. Der Vertrag für List<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.
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 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.
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 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 , der IList<T>
/ ICollection<T>
korrekt verwendet . Sie können nicht garantieren, dass Sie einen IList<T>
/ ICollection<T>
in-Code ersetzen können , der verwendet wird List<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 nicht IEnumerable<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 Typ List<T>
.
Für mich IList<T>
(und ICollection<T>
) ist der schlechteste Teil des .NET Frameworks. IsReadOnly
verstößt gegen das Prinzip der geringsten Überraschung. Eine Klasse, die beispielsweise Array
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 verwenden IList<T>
statt List<T>
, fragen sie , wie sie ein Element eine hinzufügen würden IList<T>
. Wenn sie nichts wissen IsReadOnly
(und die meisten Leute nicht wissen ), dann verwenden Sie es nicht IList<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
IsReadOnly
es sich true
um eine IList
handelt! Vielleicht sollte diese Eigenschaft benannt werden IsFixedSize
?
Array
als getestet IList
, deshalb konnte ich die aktuellen Mitglieder ändern)
IReadOnlyCollection<T>
und IReadOnlyList<T>
die passen fast immer besser als IList<T>
, haben aber nicht die gefährliche faule Semantik von IEnumerable<T>
. Und sie sind kovariant. Vermeiden Sie IList<T>
!
List<T>
ist eine spezifische Implementierung von IList<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 Sie IList<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 .NET LinkedList<T>
dies nicht tut implementiert IList<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 die IList<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>
, die IList<T>
erweitert wird.
LinkedList<T>
gegen Nehmen List<T>
gegen IList<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.
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
class A : B, IList<T> { ... }
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.
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> verwendet haben, konnte nur eine List <Bar> -Instanz übergeben werden.
Der IList <Bar> Weg ist lockerer gekoppelt als der List <Bar> Weg.
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.
Reverse
und ToArray
sind Erweiterungsmethoden für die IEnumerable <T> -Schnittstelle, von der IList <T> abgeleitet ist.
List
s und nicht in anderen Implementierungen von sinnvoll sind IList
.
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.
Was passiert , wenn .NET 5.0 ersetzt System.Collections.Generic.List<T>
zu System.Collection.Generics.LinearList<T>
. .NET besitzt immer den Namen, List<T>
aber sie garantieren, dass IList<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
Grundsätzlich werden in den meisten der obigen Antworten alle Konzepte angegeben, warum die Schnittstelle über konkreten Implementierungen verwendet wird.
IList<T> defines those methods (not including extension methods)
IList<T>
MSDN-Link
List<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-Link
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.
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
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.
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>
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.