Warum nicht generische ICollection implementieren IReadOnlyCollection in .NET 4.5?
In .NET 4.5 /C# 5, IReadOnlyCollection<T>
wird deklariert mit einem Count
Eigenschaft:
public interface IReadOnlyCollection<out T> : IEnumerable<T>, IEnumerable
{
int Count { get; }
}
Frage ich mich, wäre es nicht sinnvoll für ICollection<T>
zur Umsetzung der IReadOnlyCollection<T>
- Schnittstelle als auch:
public interface ICollection<T> : IEnumerable<T>, IEnumerable, *IReadOnlyCollection<T>*
Diese habe gemeint, die Klassen der Umsetzung ICollection<T>
haben, würde automatisch umgesetzt IReadOnlyCollection<T>
. Das klingt mir vernünftig.
Den ICollection<T>
Abstraktion betrachtet werden kann als eine Verlängerung der IReadOnlyCollection<T>
Abstraktion. Beachten Sie, dass List<T>
, zum Beispiel, implementiert sowohl ICollection<T>
und IReadOnlyCollection<T>
.
Aber es hat nicht so ausgelegt.
Was vermisse ich hier? Warum sollte der aktuellen Implementierung wurden stattdessen ausgewählt?
UPDATE
Ich bin auf der Suche nach einer Antwort, die verwendet objektorientierten design-Argumentation zu erklären, warum:
- Eine konkrete Klasse, wie
List<T>
Umsetzung sowohlIReadOnlyCollection<T>
undICollection<T>
ist ein besseres design als:
ICollection<T>
UmsetzungIReadOnlyCollection<T>
direkt
Bitte beachten Sie auch, dass dies im Grunde die gleiche Frage wie:
- Warum nicht
IList<T>
implementierenIReadOnlyList<T>
? - Warum nicht
IDictionary<T>
implementierenIReadOnlyDictionary<T>
?
- Wie würde sich das auf die rückwärts-Kompatibilität?
- ist es nicht so weit, wie ich sehen kann.. es sei denn, Sie liefern ein Gegenbeispiel?
- Sie sind speziell beschlossen, um das brechen ändern, hinzufügen, die Lesen nur die entsprechenden Schnittstellen zu den Sammlungen .NET. (Wissend, dass die Fälle der tatsächlichen break-code sind eher künstlich und nicht wahrscheinlich zu sein weit verbreitet in der Produktion von code.)
- Naja, auf technischer Ebene, so ziemlich alles ist eine wichtige änderung. Durch das hinzufügen einer neuen Schnittstelle, die es betreffen könnte Methode überlast Auflösung, zum Beispiel.
- Ich bin noch nicht überzeugt, welche Seite hat das beste argument. Nachdem die Seiten gewechselt, mehrmals, jetzt Neige ich dazu zu neigen "
ICollection<T>
nicht implementieren sollteIReadOnlyCollection<T>
undList<T>
UmsetzungIReadOnlyList<T>
macht keinen Sinn". Die größere Frage ist, wenn die KlasseC
implementiert das interfaceI
bedeutet das, dassC
könnenI
oderC
sollteI
. Finden Sie ein gleichwertiges Frage hier: why-does-listt-implement-ireadonlylistt-in-net-4-5. Zum nachdenken anregende Frage btw, +1! - Für statisch kompilierten code, überlast-Auflösung ist schon ein problem gelöst. Es gibt "targeting-packs" oder "reference assemblies", in denen nur die Typen in der framework-version, die Sie sind targeting. Der compiler erkennt diese und wird die überlast Auflösung die gleiche Weise, unabhängig davon, welche Laufzeit Ihr code ausgeführt wird, auf. Ihr Problem ist nur relevant, mit dynamischen code oder Reflexion, beide benötigen sorgfältig und sollte vermieden werden, wenn möglich, aus diesem und anderen Gründen. Immer anders Verhalten, wenn retargeting ist ein erwartet durch den Programmierer.
- Ja, Wenn Sie als Ziel eine ältere .NET-runtime-Ihr code wird weiterhin funktionieren, sogar wenn der computer mit dem code hat eine neuere runtime. Die unterbrechende änderung, die ich meinte ist hier, dass, wenn Sie code kompiliert, sagen, .NET 3, dann ändern Sie die Laufzeit auf .NET 4.5, Sie können es nicht mehr kompilieren. Das ist eine wichtige änderung der Laufzeit änderung der Codebasis auf, dass die Laufzeit machen kann der code nicht mehr kompilieren.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Jon hatte Recht, hier https://stackoverflow.com/a/12622784/395144 , markieren Sie seine Antwort als die Antwort:
Da Schnittstellen können über explizite Implementierungen, Gewinnung von Basis-interfaces ist nicht rückwärts kompatibel (mit Basis-Klassen, die Sie haben dieses problem nicht).
Deswegen...
... aber nicht deren interfaces.
IMHO, Sie haben einen design-Fehler anfangs ziemlich unlösbar nun (ohne etwas kaputt zu machen).
EDIT: verstecken hilft nicht, alte (explicit) - Implementierungen nicht noch bauen (ohne den code ändern):
ICollection
undIReadOnlyCollection
definieren könnenCount
(die abgeleitete Klasse verstecken, die base class-member mitnew
Stichwort). Sie können sehen, ein Beispiel, wo dies geschieht im die Antwort von Eric Lippert. So, zwar ein guter Punkt, in den sich Ihre Antwort ist das nicht Grund genug, um wählen Sie das design Sie haben.new T Get();
mit so etwas wieusing INew<T>.Get();
(weitgehend vergleichbar mit einer 'mit' - syntax, die in C++).Gibt es wohl mehrere Gründe. Hier sind einige:
Riesigen rückwärts-Kompatibilität Probleme
Wie würden Sie schreiben, die definition von
ICollection<T>
? Das sieht natürlich:Aber es hat ein problem, weil
IReadOnlyCollection<T>
auch erklärtCount
Eigenschaft (der compiler eine Warnung hier). Abgesehen von der Warnung, verlassen es wie es ist (was gleichbedeutend ist mit schreibennew int Count
) ermöglicht implementors haben verschiedenen Implementierungen für die beidenCount
Eigenschaften durch Umsetzung von mindestens einem explizit. Dies könnte "amüsant", wenn die beiden Implementierungen entschieden unterschiedliche Werte zurückgeben. So dass die Menschen zu Schießen selbst in den Fuß ist eher nicht C#'s Stil.OK, also was ist mit:
Gut, dieser bricht alle bestehenden code, beschlossen, die
Count
ausdrücklich:Daher scheint es mir, dass es keine gute Lösung für dieses problem: entweder Sie brechen den bestehenden code, oder Sie zwingen, eine fehleranfällige Umsetzung auf jeder. So die einzige Siegeszug ist nicht zu spielen.
(new List<string>() is IReadOnlyCollection<Foo>)
ausgewertettrue
.ICollection
erklärt, seine eigenenCount
Eigenschaft?UnluckyClass
würde noch kompilieren, aber jetzt würde es sich anders Verhalten, als zuvor.if(true)
, so wertlos.List<T>
"opts-in" durch die Implementierung der beidenIReadOnlyCollection<T>
undICollection<T>
statt nurICollection<T>
). Sie sagen, dassif (collection is IReadOnlyCollection<Foo>)
ist nur sinnvoll, wennICollection<T>
nicht implementiertIReadOnlyCollection<T>
. Ich kann nicht erkennen, wenn dieses Verhalten sei "in der Praxis"—es macht nur eine Methode aufrufen, die akzeptiertIReadOnlyCollection<T>
counterintuitively härter (z.B., wenn Sie übergeben möchtenIList<T>
ist).IList
undIReadOnlyCollection
sind semantisch sehr unterschiedlich. Es gibt absolut keine Garantie, dass etwas, das "ist eine Liste" auch "ist ein nur-lese-Kollektion", warum also sollte die Umstellung automatisch? Ich weiß, es scheint umständlich zu Zeiten, aber IMO ist das einfach ein Indiz für eine unvollkommene design.IReadOnlyCollection<T>
zu ermöglichen, Sie schreiben eine Methode, die erklärt, "ich brauche nur zu Lesen, diese Sammlung, ich verspreche, ich werde nicht hinzufügen oder entfernen Sie alle Gegenstände" (ignoriert die Möglichkeit der runtime-Gießen, ew). Sehr nützlich für das schreiben von generischem code, der kümmert sich nicht, wenn die Auflistung der Elemente ist veränderlich ist oder nicht. Als ein weiteres Beispiel, eine Datei schreibgeschützt geöffnet durch einen Prozess, der die Fähigkeit hat, ändern Sie die hinter dem Prozess zurück. Doch dieser Prozess lässt sich nicht öffnen, die Datei im append-Modus, weil er durchführen wollte, Lesen Sie nur Operationen.ICollection.IsReadOnly
Eigenschaft, die dem Zweck dient, die Sie zu sein scheinen nach. Run-time type checks sind der falsche Mechanismus für den job.Wäre es semantisch falsch, da offensichtlich nicht jeder
ICollection
ist schreibgeschützt.Sagte, Sie hätten genannt, die Schnittstelle
IReadableCollection
, während ein Umsetzung genannt werden könnteReadOnlyCollection
.Allerdings hat Sie nicht diesen Weg gehen. Warum? Ich sah eine BCL-team-member schreiben, dass Sie nicht wollte, dass die collections-API, um sich zu verworren. (Obwohl ist das schon, ehrlich gesagt.)
List<T>
implementierenIReadOnlyCollection<T>
.List<T>
ist semantisch falsch, denn Sie setztIReadOnlyCollection<T>
und natürlichList<T>
ist nicht schreibgeschützt.List<T>
implementierenIReadOnlyCollection<T>
ICollection<T>
Erben sollen vonIReadableCollection<out T>
undIWritableCollection<in T>
aktivieren ko - und Kontravarianz auf jede Sammlung, wenn "gesehen" über diese Schnittstellen.ICollectionReader<out T>
undICollectionWriter<in T>
dann.Wenn ich Ihre Frage zum ersten Lesen dachte ich "Hey, er hat Recht! Das würde am meisten Sinn machen." Aber der Punkt, der eine Schnittstelle zu beschreiben, einen Vertrag - Eine Beschreibung des Verhaltens. Wenn die Klasse implementiert
IReadOnlyCollection
es sollte nur gelesen werden.Collection<T>
nicht. Also für eineCollection<T>
eine Schnittstelle implementieren, die implizit schreibgeschützt könnte dazu führen, einige ziemlich unangenehme Probleme. Verbraucher in einemIReadOnlyCollection
zu machen sind bestimmte Annahmen über das zugrunde liegende Sammlung - wie iteriert ist es sicher, weil niemand kann es ändern, etc. etc. Wenn es strukturiert war, wie Sie vorschlagen, dass kann nicht wahr sein!List<T>
wurden geändert, um die Umsetzung IReadOnlyCollection und ICollection... so würde kein Verbraucher der Liste in das aktuelle design haben das gleiche problem wie du festgestellt hast?IReadableCollection<out T>
denen gemacht kein Versprechen unveränderlich, oder eine SchnittstelleIImmutableCollection<T>
die versprochen auf immer und ewig enthalten den gleichen Satz von T ist. Ich kann nicht denken, der viel Einsatz für eine Schnittstelle, die verspricht, dass das Objekt "nur-Lesen" aber nicht garantieren, dass es kapselt eine unveränderliche Folge.IReadOnlyCollection<T>
ist für die Funktionalität, die sollte genannt wordenIReadableCollection<T>
.IReadOnlyCollection<T>
ist, dass Sie behandeln können sowohl unveränderliche und veränderliche Listen, die als nur-lese-Liste. Es lässt Sie definieren die Verbraucher die nur Lesen eine indizierte lsit von Werten arbeiten mit jeder Art von Liste, denn auch veränderliche Listen haben die Fähigkeit zu bleiben statisch, wenn Sie Zugang zu Ihnen durch eineIReadOnly
- Schnittstelle. Warum würden Sie nicht wollen, zu können, übergeben Sie Ihre veränderliche Liste, um eine Funktion, die führt einige nützliche Aufgabe, mit einem readonly-indizierte Liste als input?IReadOnlyCollection
implementiert eine ausreichende API zu behandeln, als ein nur-lese-Kollektion. Wenn der Zugriff über eine Referenz des TypsIReadOnlyCollection
Sie werden nur in der Lage, es zu benutzen als nur-lese-Kollektion. Ob es implementiert eine weitere API zu handeln, wie eine beschreibbare Sammlung, eine Datenbank-client, oder eine giraffe ist völlig irrelevant von einem OOP-Standpunkt aus.Objekt-Orientierte design-Argumentation würde, stammen aus der "Ein" - Beziehung zwischen einer Klasse und alle Schnittstellen, die die Klasse implementiert.
In .NET 4.5/c# 5.0
List<T>
ist sowohl eineICollection<T>
undIReadOnlyCollection<T>
. Sie können ein Exemplar einerList<T>
entweder als Typ, je nachdem, wie Sie möchten, dass Ihre Sammlung, die verwendet werden.Während
ICollection<T>
implementierenIReadOnlyCollection<T>
würde Ihnen die Fähigkeit zu habenList<T>
entweder einICollection<T>
oderIReadOnlyCollection<T>
wäre zu sagen, dassICollection<T>
"Ein"IReadOnlyCollection<T>
was er nicht ist.Aktualisiert
Wenn jede Klasse, die geerbt von
ICollection<T>
umgesetztIReadOnlyCollection<T>
dann würde ich sagen, es macht mehr Sinn zu habenICollection<T>
die das interface implementieren. Da das nicht der Fall ist, überlegen Sie, was es Aussehen würde, wennICollection<T>
setzenIReadOnlyCollection<T>
:public interface IReadOnlyCollection<T> : IEnumerable<T>, IEnumerable{}
public interface ICollection<T> : IReadOnlyCollection<T> {}
Wenn Sie einmal die Signatur für
ICollection<T>
, es sieht aus wie es IST-EIN Lesen-nur Abholung. Warten, schauen wir uns IReadOnlyCollection und ahh...es implementiertIEnumerable<T>
undIEnumerable
also es muss nicht unbedingt eine Lesen-nur-Sammlung. Funktional sind die Implementierungen vorgeschlagen, die von der ursprünglichen Frage sind die gleichen, aber semantisch glaube ich, ist es richtiger zu schieben, eine interface-Implementierung so hoch wie es gehen kann.List<T>
entweder als Typ; es ist eine Klasse von Rahmen und bereits umgesetzt für Sie.IReadOnlyCollection<T> collection = new List<T>();
. Wenn Sie entwerfen einen Rahmen, wo Sie nur zurückkehren wollte, ein nur-lese-Kollektion haben Sie vielleicht eine Eigenschaft oder Methode RückgabewertIReadOnlyCollection
statt einerList
,ICollection
,IEnumerable
usw.List<T>
IST-EINIReadOnlyCollection<T>
, dann mit der gleichen Argumentation folgt, dassICollection<T>
IST-EINIReadOnlyCollection<T>
List<T>
IST-EINIReadOnlyCollection<T>
weilList<T>
implementiertIReadOnlyCollection<T>
.ICollection<T>
nicht implementiertIReadOnlyCollection<T>
daherICollection<T>
IST-NICHT-EINIReadOnlyCollection<T>
(und das sollte nicht sein).ICollection<T>
hab implementierenIReadOnlyCollection<T>
dann wäre es einIReadOnlyCollection<T>
? Warum sollte die Liste sein, nur-lese-und ICollection nicht? Was ist so gelesen-nur über eine Liste, die nicht auch über eine ICollection?Diese Schnittstelle ist mehr eine Beschreibung Schnittstelle dann wirklich funktionell Sache.
In vorgeschlagener Ansatz jede Sammlung muss so verstanden werden, als etwas, das man onlly Lesen ad ist immer das gleiche. So konnte man nicht hinzufügen oder entfernen, eine Sache nach der Schöpfung.
Könnten wir Fragen, unser selbst, warum die Schnittstelle aufgerufen wird
IReadOnlyCollection
, nicht 'IReadOnlyEnumerable", wie es zu verwendenIEnumerable<T>, IEnumerable
. Die Antwort ist einfach, diese Art von Schnittstelle haben und nicht senece, weil Enumerable ist schon 'nur Lesen'.So lässt Blick in doc IReadOnlyCollection(T)
Den ersten (und letzten) senetece in der Beschreibung sagt:
Und ich denke, dass dies alles erklären, wenn du weißt, was starke-Eingabe ist für.