Rückgabe von 'IList' gegen 'ICollection' gegen 'Collection'

119

Ich bin verwirrt darüber, welchen Auflistungstyp ich von meinen öffentlichen API-Methoden und -Eigenschaften zurückgeben soll.

Die Sammlungen , die ich im Sinn haben sind IList, ICollectionund Collection.

Wird die Rückgabe eines dieser Typen immer den anderen vorgezogen oder hängt dies von der jeweiligen Situation ab?

Rocky Singh
quelle

Antworten:

71

Im Allgemeinen sollten Sie einen Typ zurückgeben, der so allgemein wie möglich ist, dh einen Typ, der gerade genug über die zurückgegebenen Daten weiß, die der Verbraucher verwenden muss. Auf diese Weise haben Sie mehr Freiheit, die Implementierung der API zu ändern, ohne den Code zu beschädigen, der sie verwendet.

Betrachten Sie auch die IEnumerable<T>Schnittstelle als Rückgabetyp. Wenn das Ergebnis nur iteriert werden soll, benötigt der Verbraucher nicht mehr.

Guffa
quelle
25
Berücksichtigen Sie jedoch auch ICollection <T>, das IEnumerable <T> erweitert, um zusätzliche Dienstprogramme einschließlich einer Count-Eigenschaft bereitzustellen. Dies kann hilfreich sein, da Sie die Länge der Sequenz bestimmen können, ohne die Sequenz mehrmals zu durchlaufen.
Retrodrone
11
@retrodrone: Tatsächlich Countüberprüft die Implementierung der Methode den tatsächlichen Typ der Auflistung, sodass die Eigenschaften Lengthoder Countfür einige bekannte Typen wie Arrays verwendet werden und ICollectionselbst wenn Sie sie als angeben IEnumerable.
Guffa
8
@Guffa: Es kann sinnvoll sein zu beachten, dass, wenn eine Klasse das nicht generische Thing<T>implementiert, IList<T>aber nicht das generische ICollection, das Aufrufen IEnumerable<Cat>.Count()von a Thing<Cat>schnell ist, das Aufrufen IEnumerable<Animal>.Count()jedoch langsam ist (da die Erweiterungsmethode nach einer Implementierung von suchen und diese nicht finden würde ICollection<Cat>). Wenn die Klasse das Nicht-Generische implementiert ICollection, IEnumerable<Animal>.Countwürde sie dies jedoch finden und verwenden.
Supercat
8
Ich bin nicht einverstanden. Es ist am besten, den reichhaltigsten Typ zurückzugeben, den Sie haben, damit der Client ihn direkt verwenden kann, wenn er dies wünscht. Wenn Sie eine Liste <> haben, warum sollten Sie eine IEnuerable <> zurückgeben, um den Aufrufer zu zwingen, ToList () unnötig aufzurufen?
Zumalifeguard
140

ICollection<T>ist eine Schnittstelle , die Sammlung Semantik wie aussetzt Add(), Remove()und Count.

Collection<T>ist eine konkrete Implementierung der ICollection<T>Schnittstelle.

IList<T>ist im Wesentlichen ein ICollection<T>mit zufälliger Reihenfolge basierender Zugriff.

In diesem Fall sollten Sie entscheiden, ob Ihre Ergebnisse eine Listensemantik wie die auftragsbasierte Indizierung erfordern (dann verwenden IList<T>) oder ob Sie nur eine ungeordnete "Tasche" von Ergebnissen zurückgeben müssen (dann verwenden ICollection<T>).

Cordialgerm
quelle
5
Collection<T>Geräte IList<T>und nicht nur ICollection<T>.
CodesInChaos
56
Alle Sammlungen in .Net sind bestellt, weil IEnumerable<T>bestellt. Was unterscheidet IList<T>aus ICollection<T>ist , dass es Mitglieder an der Arbeit mit Indizes bietet. Zum Beispiel list[5]funktioniert, wird aber collection[5]nicht kompiliert.
Svick
5
Ich bin auch nicht der Meinung, dass geordnete Sammlungen implementiert werden sollten IList<T>. Es gibt viele bestellte Sammlungen, die dies nicht tun. IList<T>geht es um schnellen indizierten Zugriff.
CodesInChaos
4
@yoyo Die .net-Auflistungsklassen und -Schnittstellen sind einfach schlecht gestaltet und schlecht benannt. Ich würde nicht zu viel darüber nachdenken, warum sie sie so nannten.
CodesInChaos
6
@svick: Sind alle Kollektionen bestellt, weil IEnumerable<T>bestellt? Take HashSet<T>- es implementiert IEnumerable<T>, ist aber eindeutig nicht bestellt. Das heißt, Sie haben keinen Einfluss auf die Reihenfolge der Elemente und diese Reihenfolge kann sich jederzeit ändern, wenn die interne Hash-Tabelle neu organisiert wird.
Olivier Jacot-Descombes
60

Der Hauptunterschied zwischen IList<T>und ICollection<T>besteht darin, IList<T>dass Sie über einen Index auf Elemente zugreifen können. IList<T>beschreibt Array-ähnliche Typen. Auf Elemente in einem ICollection<T>kann nur durch Aufzählung zugegriffen werden. Beide ermöglichen das Einfügen und Löschen von Elementen.

Wenn Sie nur eine Sammlung aufzählen müssen, IEnumerable<T>ist dies vorzuziehen. Es hat zwei Vorteile gegenüber den anderen:

  1. Änderungen an der Sammlung sind nicht zulässig (jedoch nicht an den Elementen, wenn sie vom Referenztyp sind).

  2. Es ermöglicht die größtmögliche Vielfalt an Quellen, einschließlich Aufzählungen, die algorithmisch generiert werden und überhaupt keine Sammlungen sind.

Collection<T>ist eine Basisklasse, die hauptsächlich für Implementierer von Sammlungen nützlich ist. Wenn Sie es in Schnittstellen (APIs) verfügbar machen, werden viele nützliche Sammlungen ausgeschlossen, die nicht davon abgeleitet sind.


Ein Nachteil von IList<T>ist, dass Arrays es implementieren, Sie jedoch keine Elemente hinzufügen oder entfernen können (dh Sie können die Array-Länge nicht ändern). Eine Ausnahme wird ausgelöst, wenn Sie IList<T>.Add(item)ein Array aufrufen . Die Situation ist etwas entschärft, ebenso IList<T>wie eine boolesche Eigenschaft IsReadOnly, die Sie überprüfen können, bevor Sie dies versuchen. Aber in meinen Augen ist dies immer noch ein Designfehler in der Bibliothek . Daher verwende ich List<T>direkt, wenn die Möglichkeit zum Hinzufügen oder Entfernen von Elementen erforderlich ist.

Olivier Jacot-Descombes
quelle
Code - Analyse (und FxCop) darauf hin , dass , wenn eine konkrete generische Auflistung der Rückkehr eines der folgenden zurückgegeben werden soll: Collection, ReadOnlyCollectionoder KeyedCollection. Und das Listsollte nicht zurückgegeben werden.
DavidRR
@DavidRR: Oft ist es nützlich, eine Liste an eine Methode zu übergeben, die Elemente hinzufügen oder entfernen muss. Wenn Sie den Parameter als eingeben IList<T>, kann nicht sichergestellt werden, dass die Methode nicht mit einem Array als Parameter aufgerufen wird.
Olivier Jacot-Descombes
7

IList<T>ist die Basisschnittstelle für alle generischen Listen. Da es sich um eine geordnete Sammlung handelt, kann die Implementierung über die Reihenfolge entscheiden, die von der sortierten Reihenfolge bis zur Einfügereihenfolge reicht. Darüber hinaus Ilistverfügt Item über die Eigenschaft Item, mit der Methoden Einträge in der Liste basierend auf ihrem Index lesen und bearbeiten können. Dies ermöglicht das Einfügen und Entfernen eines Werts in / aus der Liste an einem Positionsindex.

Auch da stehen hier auch IList<T> : ICollection<T>alle Methoden von ICollection<T>zur Implementierung zur Verfügung.

ICollection<T>ist die Basisschnittstelle für alle generischen Sammlungen. Es definiert Größe, Enumeratoren und Synchronisationsmethoden. Sie können ein Element zu einer Sammlung hinzufügen oder daraus entfernen, aber Sie können nicht auswählen, an welcher Position es aufgrund des Fehlens einer Indexeigenschaft geschieht.

Collection<T>eine Implementierung sieht IList<T>, IListund IReadOnlyList<T>.

Wenn Sie einen engeren Schnittstellentyp verwenden, z. B. ICollection<T>anstelle von IList<T>, schützen Sie Ihren Code vor Änderungen. Wenn Sie einen breiteren Schnittstellentyp verwenden, z. B. IList<T>besteht die Gefahr, dass Codeänderungen beschädigt werden.

Zitat aus einer Quelle ,

ICollection, ICollection<T>: Sie möchten die Sammlung ändern oder interessieren sich für ihre Größe. IList, IList<T>: Sie möchten die Sammlung ändern und kümmern sich um die Reihenfolge und / oder Positionierung der Elemente in der Sammlung.

NullReference
quelle
5

Die Rückgabe eines Schnittstellentyps ist allgemeiner, daher würde ich mich (ohne weitere Informationen zu Ihrem speziellen Anwendungsfall) dazu neigen. Wenn Sie die Indizierungsunterstützung verfügbar machen möchten, wählen Sie IList<T>, andernfalls ICollection<T>reicht dies aus. Wenn Sie angeben möchten, dass die zurückgegebenen Typen schreibgeschützt sind, wählen Sie IEnumerable<T>.

Und, falls Sie es nicht vorher gelesen haben, Brad Abrams und Krzysztof Cwalina ein großes Buch schrieb dem Titel „Framework Design Guidelines: Conventions, Idiome und Muster für wiederverwendbare .NET - Bibliotheken“ (Sie können ein Download von verdauen hier ).

Alan
quelle
IEnumerable<T>ist schreibgeschützt? Sicher, aber wenn Sie es in einem Repository-Kontext neu abstimmen, ist es auch nach der Ausführung von ToList nicht threadsicher. Problem ist, wenn die ursprüngliche Datenquelle GC erhält, dann funktioniert IEnumarble.ToList () nicht in einem Multithread-Kontext. Es funktioniert linear, da GC aufgerufen wird, nachdem Sie die Arbeit asyncerledigt haben, aber jetzt einen Tag in 4.5+ IEnumarbale zu verwenden, wird zu einem Ballschmerz.
Piotr Kula
-3

Es gibt einige Themen, die aus dieser Frage stammen:

  • Schnittstellen versus Klassen
  • Welche spezifische Klasse aus mehreren ähnlichen Klassen, Sammlungen, Listen, Arrays?
  • Gemeinsame Klassen im Vergleich zu Unterelementensammlungen ("Generika")

Möglicherweise möchten Sie hervorheben, dass es sich um eine objektorientierte API handelt

Schnittstellen versus Klassen

Wenn Sie nicht viel Erfahrung mit Schnittstellen haben, empfehle ich, sich an Klassen zu halten. Ich sehe viele Male, wie Entwickler zu Schnittstellen springen, auch wenn dies nicht unbedingt erforderlich ist.

Und am Ende ein schlechtes Interface-Design anstelle eines guten Klassendesigns, das übrigens irgendwann auf ein gutes Interface-Design migriert werden kann ...

Sie werden viele Schnittstellen in der API sehen, aber beeilen Sie sich nicht, wenn Sie sie nicht benötigen.

Sie werden schließlich lernen, wie Sie Schnittstellen auf Ihren Code anwenden.

Welche spezifische Klasse aus mehreren ähnlichen Klassen, Sammlungen, Listen, Arrays?

In c # (Dotnet) gibt es mehrere Klassen, die ausgetauscht werden können. Wenn Sie, wie bereits erwähnt, etwas aus einer spezifischeren Klasse wie "CanBeSortedClass" benötigen, machen Sie es in Ihrer API explizit.

Muss Ihr API-Benutzer wirklich wissen, dass Ihre Klasse sortiert werden kann, oder ein bestimmtes Format auf die Elemente anwenden? Verwenden Sie dann "CanBeSortedClass" oder "ElementsCanBePaintedClass", andernfalls "GenericBrandClass".

Verwenden Sie andernfalls eine allgemeinere Klasse.

Gemeinsame Sammlungsklassen im Vergleich zu Sammlungen von Unterelementen ("Generika")

Sie werden feststellen, dass es Klassen gibt, die andere Elemente enthalten, und Sie können angeben, dass alle Elemente von einem bestimmten Typ sein sollen.

Generische Sammlungen sind Klassen, mit denen Sie dieselbe Sammlung für mehrere Codeanwendungen verwenden können, ohne für jeden neuen Unterelementtyp eine neue Sammlung erstellen zu müssen, z. B.: Sammlung .

Benötigt Ihr API-Benutzer einen ganz bestimmten Typ, der für alle Elemente gleich ist?

Verwenden Sie so etwas wie List<WashingtonApple>.

Benötigt Ihr API-Benutzer mehrere verwandte Typen?

Expose List<Fruit>für Ihre API und verwenden List<Orange> List<Banana>, List<Strawberry>intern, wo Orange, Bananaund Strawberrysind Nachkommen aus Fruit.

Benötigt Ihr API-Benutzer eine generische Typensammlung?

Verwenden Sie List, wo alle Elemente sind object.

Prost.

umlcat
quelle
7
"Wenn Sie nicht viel Erfahrung mit Schnittstellen haben, empfehle ich, sich an Klassen zu halten." Dies ist kein guter Rat. Das ist so, als würde man sagen, wenn es etwas gibt, das man nicht weiß, halte sich einfach davon fern. Schnittstellen sind äußerst leistungsfähig und stützen das Konzept "Programmieren auf Abstraktionen vs. Konkretionen". Sie sind auch sehr leistungsfähig für Unit-Tests, da Typen über Mocks ausgetauscht werden können. Es gibt eine Reihe weiterer wichtiger Gründe, Schnittstellen zu verwenden, die über diese Kommentare hinausgehen. Erkunden Sie sie daher auf jeden Fall.
Atconway
@atconway Ich verwende regelmäßig Schnittstellen, aber da viele Konzepte ein leistungsstarkes Tool sind, kann es einfach zu mischen sein. Und manchmal braucht der Entwickler es nicht. Mein Vorschlag war nicht "Niemals Schnittstellen", sondern "In diesem Fall können Sie die Schnittstellen überspringen. Erfahren Sie mehr darüber, und Sie können später das Beste daraus machen". Prost.
Umlcat
1
Ich empfehle immer auf eine Schnittstelle zu programmieren. Es hilft, den Code lose gekoppelt zu halten.
Mac10688
@ mac10688 Meine vorherige Antwort (atconway) gilt auch für Ihren Kommentar.
Umlcat