2016-07-17 13 views
1

Ich lief in einen Fall, wo die Tatsache, dass List.AsReadOnly() gibt ReadOnlyCollection anstelle von IReadOnlyCollection machte die Dinge schwer für mich. Da die zurückgegebene Sammlung die Value einer Dictionary war, konnte sie nicht automatisch auf eine IReadOnlyCollection umgestellt werden. Das schien merkwürdig, und nach Überprüfung des .Net-Quellcodes habe ich bestätigt, dass die AsReadOnly()-Methode für List etwas anderes tut als für Dictionary, nämlich das Zurückgeben der konkreten Klasse anstelle der Schnittstelle.Warum gibt List.AsReadOnly eine ReadOnlyCollection zurück, aber Dictionary.AsReadOnly gibt ein IReadOnlyDictionary zurück?

Kann jemand erklären, warum das ist? Es scheint ein Nachteil, diese Inkonsistenz zu haben, vor allem, weil wir die Schnittstellen möglichst und vor allem in der Öffentlichkeit nutzen wollen.

In meinem Code dachte ich zuerst, dass, da mein Konsument nur eine private Methode war, ich seine Parametersignatur von IReadOnlyDictionary<T, IReadOnlyCollection<T>> zu IReadOnlyDictionary<T, ReadOnlyCollection<T>> ändern konnte. Aber dann wurde mir klar, dass dieser es wie die private Methode aussehen lässt könnte die Sammlung Werte ändern, so habe ich eine ärgerliche explizite Umwandlung in den früheren Code, um richtig die Schnittstelle zu verwenden:

.ToDictionary(
    item => item, 
    item => (IReadOnlyCollection<T>) relatedItemsSelector(item) 
     .ToList() 
     .AsReadOnly() // Didn't expect to need the direct cast 
) 

Oh, und da Ich bekomme immer Kovarianz und Kontravarianz verwirrt, könnte mir bitte jemand sagen, welches die automatische Besetzung verhindert, und versuchen, mich auf eine vernünftige Weise daran zu erinnern, wie man sich an sie für die Zukunft erinnert? (zB Sammlungen sind nicht ______variant [co/contra] für _____ [Eingabe/Ausgabe] -Parameter.) Ich verstehe, warum dies nicht sein kann, weil es viele Implementierungen der Schnittstelle geben kann und es nicht sicher ist, alle zu konvertieren einzelne Elemente des Wörterbuchs zum gewünschten Typ. Es sei denn, ich bläst sogar diesen einfachen Aspekt und ich nicht verstehe es, in welchem ​​Fall ich hoffe, Sie können mir helfen, mich richtig zu stellen ...

Antwort

0

Der Grund ist historisch. Die IReadOnly * -Schnittstellen wurden in .NET 4.5 hinzugefügt, während List<T>.AsReadOnly() in .NET 2.0 hinzugefügt wurde. Die Änderung des Rückgabetyps wäre eine bahnbrechende Änderung gewesen.

Die explizite Besetzung ist nicht so schlecht. Es ist nicht einmal ein Laufzeit-Cast, da der Compiler dies statisch verifizieren kann (es wird kein Cast an IL gesendet). Übrigens können Sie es in IReadOnlyList<T> umwandeln, das auch indizierten Zugriff auf die Liste bietet. Sie könnten auch eine Erweiterungsmethode schreiben, die den von Ihnen benötigten Typ zurückgibt (z. B. AsReadOnlyList()).

In Bezug auf {co, contra} Varianz, finde ich es einfacher, mit dem C# keywords in (kontra) und out (kovarianten) zu erinnern. in Typ Parameter können nur als Eingabe Methode Argumente angezeigt werden, während out Typ Parameter nur als Ausgabe (Rückgabewerte) angezeigt werden können. Eine Methode, die einen Parameter akzeptiert, z.B. vom Typ Base, ist sicher, mit dem Typ Derived aufgerufen werden, daher ist es sicher zu in Parameter in dieser Richtung zu werfen. out ist genau das Gegenteil.

Zum Beispiel:

interface IIn<in T> { Set(T value); } 
IIn<Base> b = ... 
IIn<Derived> d = b; 
d.Set(derived); // safe since any method accepting Base can handle Derived 

interface IOut<out T> { T Get(); } 
IOut<Derived> d = ... 
IOut<Base> b = d; 
b.Get(); // safe since any Derived is Base 

Nicht Nur-Lese-Sammlung Schnittstellen nicht * sein kann -Variante, da sie beide in und out sein müßten, und dass unsicher sein würde. Der Compiler und die CLR erlauben das nicht. .NET hat eine Form von unsicheren Varianz mit Arrays:

var a = new[] { "s" }; 
var o = (object[])a; 
o[0] = 1; // ArrayTypeMismatchException 

Sie können sehen, wie sie dieses Chaos mit generischen Varianz vermeiden wollte. Vermutlich könnten sie schreibgeschützte Schnittstellen hinzufügen, die die Richtung(cotravariant) erlauben würden, aber ich schätze, sie fanden darin keinen großen Wert.

+0

Oh oh oh! Ich nahm an, dass es nicht automatisch umwandeln würde, weil es * nicht * kovariant war, aber jetzt sehe ich, dass, weil 'IReadOnlyCollection' nur gelesen wird, der Compiler sehen kann, dass es sicher ist, vom abgeleiteten Typ in die Basis zu konvertieren . Wenn das der Fall ist, warum musste ich dann eine explizite Besetzung machen? – ErikE

+0

Kovarianz und Kontravarianz treten nur dann auf, wenn Sie zwischen zwei generischen * Interfaces * umwandeln und den * -Typ-Parameter * ändern (von Basis zu abgeleitet oder umgekehrt). Die Besetzung, die Sie verwendet haben, ist nur eine Besetzung - sie ist erlaubt, weil 'ReadOnlyCollection ' 'IReadOnlyList ' 'implementiert. –

+0

Oh, oops, das macht Sinn. Es wird klarer. Warum konnte es nicht implizit wirken? – ErikE