Die Antworten auf Fragen wie diese: List <T> oder IList <T> scheinen immer zuzustimmen, dass die Rückgabe einer Schnittstelle besser ist als die Rückgabe einer konkreten Implementierung einer Sammlung. Aber ich kämpfe damit. Das Instanziieren einer Schnittstelle ist nicht möglich. Wenn Ihre Methode also eine Schnittstelle zurückgibt, gibt sie tatsächlich immer noch eine bestimmte Implementierung zurück. Ich habe ein bisschen damit experimentiert, indem ich 2 kleine Methoden geschrieben habe:
public static IList<int> ExposeArrayIList()
{
return new[] { 1, 2, 3 };
}
public static IList<int> ExposeListIList()
{
return new List<int> { 1, 2, 3 };
}
Und benutze sie in meinem Testprogramm:
static void Main(string[] args)
{
IList<int> arrayIList = ExposeArrayIList();
IList<int> listIList = ExposeListIList();
//Will give a runtime error
arrayIList.Add(10);
//Runs perfectly
listIList.Add(10);
}
In beiden Fällen, wenn ich versuche, einen neuen Wert hinzuzufügen, gibt mir mein Compiler keine Fehler, aber offensichtlich gibt die Methode, die mein Array als darstellt, IList<T>
einen Laufzeitfehler aus, wenn ich versuche, etwas hinzuzufügen. So können die Leute , die nicht wissen , was in meiner Methode geschieht, und Werte , um es hinzuzufügen müssen, zu gezwungen sind , kopieren Sie zuerst meine IList
auf eine List
Lage sein , Werte hinzuzufügen , ohne Fehler zu riskieren. Natürlich können sie einen Typcheck durchführen, um festzustellen, ob es sich um ein List
oder ein handelt Array
, aber wenn sie dies nicht tun und der Sammlung Elemente hinzufügen möchten, haben sie keine andere Wahl, um das IList
in ein zu kopieren List
, selbst wenn es ist schon einList
. Sollte ein Array niemals als belichtet werden IList
?
Ein weiteres Anliegen von mir basiert auf der akzeptierten Antwort auf die verknüpfte Frage (Schwerpunkt Mine):
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 vielleicht nicht so wichtig, und die Verwendung von List ist möglicherweise in Ordnung.
Stellen Sie sich vor, jemand hat meine IList<T>
von meiner ExposeListIlist()
Methode erhaltenen einfach so verwendet, um Werte hinzuzufügen / zu entfernen. Alles funktioniert gut. Aber jetzt, wie die Antwort andeutet, gebe ich ein Array anstelle einer Liste zurück (kein Problem auf meiner Seite!), Da die Rückgabe einer Schnittstelle flexibler ist.
TLDR:
1) Das Freilegen einer Schnittstelle verursacht unnötige Besetzungen? Ist das nicht wichtig?
2) Manchmal, wenn Benutzer der Bibliothek keine Besetzung verwenden, kann ihr Code beim Ändern Ihrer Methode beschädigt werden, obwohl die Methode vollkommen in Ordnung bleibt.
Ich überdenke das wahrscheinlich, aber ich bin mir nicht einig, dass die Rückgabe einer Schnittstelle der Rückgabe einer Implementierung vorzuziehen ist.
quelle
IEnumerable<T>
Sie zurück und Sie haben Kompilierungszeitsicherheit. Sie können weiterhin alle LINQ-Erweiterungsmethoden verwenden, die normalerweise versuchen, die Leistung zu optimieren, indem Sie sie in einen bestimmten Typ umwandeln (SieICollection<T>
möchten dieCount
Eigenschaft verwenden, anstatt sie aufzuzählen).IList<T>
durch einen "Hack" implementiert . Wenn Sie eine Methode verfügbar machen möchten, die sie zurückgibt, geben Sie einen Typ zurück, der sie tatsächlich implementiert.IEnumerable<T>
an erster Stelle. Dann ist es in Ordnung zurückzukehrenIList<T>
oderList<T>
.Antworten:
Vielleicht beantwortet dies Ihre Frage nicht direkt, aber in .NET 4.5+ ziehe ich es vor, diese Regeln beim Entwerfen öffentlicher oder geschützter APIs zu befolgen:
IEnumerable<T>
, wenn nur eine Aufzählung verfügbar ist;IReadOnlyCollection<T>
wenn sowohl die Aufzählung als auch die Anzahl der Elemente verfügbar sind.IReadOnlyList<T>
, wenn Aufzählung, Anzahl der Elemente und indizierter Zugriff verfügbar sind.ICollection<T>
wenn Aufzählung, Anzahl der Elemente und Änderung verfügbar sind.IList<T>
, wenn Aufzählung, Anzahl der Elemente, indizierter Zugriff und Änderung verfügbar sind.Die letzten beiden Optionen setzen voraus, dass diese Methode kein Array als
IList<T>
Implementierung zurückgeben darf .quelle
IReadOnlyCollection<T>
undICollection<T>
die für nicht lösbaren Behälter ihre Verwendung haben (zum Beispiel der spätere ist de facto ein Standard für EF Entität Unter Sammlung Navigationseigenschaften)IReadOnlyCollection<T>
undIReadOnlyList<T>
haben den gleichen Fehler wie die C ++const
. Es gibt zwei Möglichkeiten: Entweder ist die Sammlung unveränderlich, oder es ist nur Ihre Art des Zugriffs auf die Sammlung, die Sie daran hindert, sie zu ändern. Und man kann nicht voneinander unterscheiden.IEnumerable<T>
. Als API-Entwickler verfügen Sie über umfassende Kenntnisse über zulässige Anwendungsfälle. Wenn Methodenergebnisse nur aufgezählt werden sollen, gibt es keinen Grund zur OffenlegungIList<T>
. Je weniger mit dem Ergebnis zu tun haben darf, desto flexibler ist die Methodenimplementierung. WennIEnumerable<T>
Sie beispielsweise verfügbar machen, können Sie die Implementierung inyield return
s ändern , dies istIList<T>
jedoch nicht zulässig.Nein, denn der Verbraucher sollte genau wissen, was IList ist:
Sie können überprüfen
IList.IsFixedSize
undIList.IsReadOnly
und tun , was man will.Ich denke, es
IList
ist ein Beispiel für eine fette Schnittstelle, die in mehrere kleinere Schnittstellen aufgeteilt werden sollte. Außerdem verstößt sie gegen das Liskov-Substitutionsprinzip, wenn Sie ein Array als zurückgebenIList
.Lesen Sie mehr, wenn Sie eine Entscheidung über die Rückgabe der Schnittstelle treffen möchten
AKTUALISIEREN
Graben mehr und ich fand, dass
IList<T>
nicht implementiertIList
undIsReadOnly
über die Basisschnittstelle zugänglich ist,ICollection<T>
aber es gibt keineIsFixedSize
fürIList<T>
. Lesen Sie mehr darüber, warum generische IList <> nicht generische IList nicht erbt.quelle
List<T>
Semantik bereitgestellt hätte , aber es gibt wahrscheinlich gute Gründe , warum dies nicht enthalten war ... Aber ja, ich denke, Ihre Vorstellung von mehreren Schnittstellen, die die verschiedenen Funktionen abdecken, wäre eine viel fundiertere Wahl.IList<T>
ist eine fette Schnittstelle, aber es ist das Beste, was Sie von Array und Liste bekommen können. Wenn es also in mehrere Schnittstellen aufgeteilt worden wäre, könnten Sie einige Methoden möglicherweise nicht mit Listen und Arrays verwenden, sondern nur mit einer davon, selbst wenn alle Methoden, die Sie verwenden möchten (wieIList.Count
oder der Indexer), unterstützt werden. Meiner Meinung nach war es eine gute Entscheidung. Wenn esNotSupportedException
in sehr wenigen Fällen zuAdd
einem Array kommt, das man verwenden möchte , dann ist das der Deal.Wie bei allen Fragen zu "Schnittstelle versus Implementierung" müssen Sie erkennen, was das Offenlegen eines öffentlichen Mitglieds bedeutet: Es definiert die öffentliche API dieser Klasse.
Wenn Sie a
List<T>
als Mitglied verfügbar machen (Feld, Eigenschaft, Methode, ...), teilen Sie dem Verbraucher dieses Mitglieds mit: Der Typ, der durch den Zugriff auf diese Methode erhalten wirdList<T>
, ist a oder etwas, das davon abgeleitet ist.Wenn Sie nun eine Schnittstelle verfügbar machen, verbergen Sie die "Implementierungsdetails" Ihrer Klasse mithilfe eines konkreten Typs. Natürlich kann man nicht instantiate
IList<T>
, aber man kann ein verwendenCollection<T>
,List<T>
, Ableitungen davon oder Ihre eigene Art der UmsetzungIList<T>
.Die eigentliche Frage lautet "Warum
Array
implementiertIList<T>
" oder "Warum hat dieIList<T>
Schnittstelle so viele Mitglieder" .Es hängt auch davon ab, was die Verbraucher dieses Mitglieds tun sollen. Wenn Sie tatsächlich ein internes Mitglied über Ihr
Expose...
Mitglied zurückgeben, möchten Sienew List<T>(internalMember)
trotzdem ein internes Mitglied zurückgeben , da der Verbraucher andernfalls versuchen kann, es zu übertragenIList<T>
und Ihr internes Mitglied dadurch zu ändern.Wenn Sie nur erwarten, dass Verbraucher die Ergebnisse wiederholen, legen Sie sie offen
IEnumerable<T>
oderIReadOnlyCollection<T>
stattdessen.quelle
IList<T>
verfügt über Methoden zum Hinzufügen und Entfernen von Elementen, weshalb die Implementierung von Arrays schlecht ist. DieIsReadOnly
Eigenschaft ist ein Hack, um die Unterschiede zu beschönigen, wenn sie in (mindestens) zwei Schnittstellen aufgeteilt werden sollte.Seien Sie vorsichtig mit pauschalen Zitaten, die aus dem Zusammenhang gerissen werden.
Dieses Zitat ist nur dann sinnvoll, wenn es im Kontext der SOLID-Prinzipien verwendet wird . Es gibt 5 Prinzipien, aber für die Zwecke dieser Diskussion werden wir nur über die letzten 3 sprechen.
Prinzip der Abhängigkeitsinversion
Meiner Meinung nach ist dieses Prinzip am schwierigsten zu verstehen. Wenn Sie sich das Zitat jedoch genau ansehen, ähnelt es Ihrem ursprünglichen Zitat.
Das ist immer noch etwas verwirrend, aber wenn wir anfangen, die anderen Prinzipien zusammen anzuwenden, macht es viel mehr Sinn.
Liskov-Substitutionsprinzip
Wie Sie bereits betont haben, unterscheidet sich die Rückgabe von an
Array
deutlich von der Rückgabe von aList<T>
, obwohl beide implementiert sindIList<T>
. Dies ist mit Sicherheit eine Verletzung von LSP.Es ist wichtig zu erkennen, dass es bei Schnittstellen um den Verbraucher geht . Wenn Sie eine Schnittstelle zurückgeben, haben Sie einen Vertrag erstellt, in dem alle Methoden oder Eigenschaften dieser Schnittstelle verwendet werden können, ohne das Verhalten des Programms zu ändern.
Prinzip der Schnittstellentrennung
Wenn Sie eine Schnittstelle zurückgeben, sollten Sie die clientseitigste Schnittstelle zurückgeben, die Ihre Implementierung unterstützt. Mit anderen Worten, wenn Sie nicht erwarten, dass der Client die
Add
Methode aufruft, sollten Sie keine Schnittstelle mit einerAdd
Methode darauf zurückgeben.Leider sind die Schnittstellen im .NET Framework (insbesondere die frühen Versionen) nicht immer ideale clientspezifische Schnittstellen. Obwohl, wie @Dennis in seiner Antwort hervorhob, in .NET 4.5+ viel mehr Auswahlmöglichkeiten bestehen.
quelle
IList<T>
Die Rückgabe einer Schnittstelle ist nicht unbedingt besser als die Rückgabe einer konkreten Implementierung einer Sammlung. Sie sollten immer einen guten Grund haben, eine Schnittstelle anstelle eines konkreten Typs zu verwenden. In Ihrem Beispiel erscheint es sinnlos, dies zu tun.
Gültige Gründe für die Verwendung einer Schnittstelle können sein:
Sie wissen nicht, wie die Implementierung der Methoden aussehen wird, die die Schnittstelle zurückgeben, und es kann viele geben, die im Laufe der Zeit entwickelt wurden. Es können andere Leute sein, die sie schreiben, von anderen Firmen. Sie möchten sich also nur auf das Nötigste einigen und es ihnen überlassen, wie die Funktionalität implementiert wird.
Sie möchten einige allgemeine Funktionen unabhängig von Ihrer Klassenhierarchie typsicher machen. Objekte verschiedener Basistypen, die dieselben Methoden bieten sollten, würden Ihre Schnittstelle implementieren.
Man könnte argumentieren, dass 1 und 2 im Grunde der gleiche Grund sind. Es sind zwei verschiedene Szenarien, die letztendlich zu demselben Bedarf führen.
"Es ist ein Vertrag". Wenn der Vertrag mit Ihnen abgeschlossen ist und Ihre Anwendung sowohl hinsichtlich der Funktionalität als auch der Zeit geschlossen ist, macht es häufig keinen Sinn, eine Schnittstelle zu verwenden.
quelle