Beginnen wir mit der zweiten Zeile, die am einfachsten ist.
Diese Umwandlung funktioniert, weil der Typparameter von IEnumerable<T>
jetzt kovariante (das ist die out
in out T
). Das bedeutet, dass Sie IEnumerable<Derived>
in ein IEnumerable<Base>
frei umwandeln können.
Die erste Zeile, die scheinbar der gleiche Fall ist, funktioniert nicht, weil int
ein Werttyp ist. Die Schnittstellenvarianz funktioniert überhaupt nicht mit Werttypen, da Werttypen nicht wirklich von System.Object
; Sie können in ein object
eingerahmt werden, aber das ist nicht das Gleiche. Die Dokumentation erwähnt das
Varianz gilt nur für Referenztypen; wenn Sie einen Werttyp angeben Für einen variant type -Parameter ist dieser type-Parameter invariant für resultierender konstruierter Typ.
Schließlich funktioniert die dritte Zeile nicht, weil der Typparameter von List<T>
ist invariant . Sie können sehen, dass es keinen out
auf seinem Typparameter gibt; Die Regeln verbieten das, weil List<T>
keine Schnittstelle ist:
In .NET Framework 4 sind die Parameter des Varianttyps beschränkt auf generische Schnittstelle und generische Delegattypen.
Die Definition jedes Typs, der von System.ValueType
abgeleitet wird, mit Ausnahme von System.Enum
, definiert tatsächlich zwei Arten von Dingen: einen Heap-Objekttyp und einen Speicherorttyp. Instanzen der letzteren können implizit in die erstere konvertiert werden (indem eine Kopie der darin enthaltenen Daten erstellt wird), und Instanzen der ersteren können explizit auf letztere typisiert werden (ebenfalls); obwohl beide Arten von Dingen durch die gleiche System.Type
beschrieben werden, und obwohl sie die gleichen Mitglieder haben, verhalten sie sich sehr unterschiedlich.
A List<AnyClassType>
erwartet eine Menge von Heap-Objektreferenzen; Ob die fragliche Liste ein List<String>
, List<StringBuilder>
, List<Button>
oder was auch immer ist, mag für die Benutzer der Liste von Interesse sein, ist aber nicht wirklich von Interesse für List<T>
selbst. Wenn man ein List<Button>
auf ein IEnumerable<Control>
umsetzt, erwartet jemand, der seine GetEnumerator()
-Methode aufruft, ein Objekt, das Referenzen auf Heap-Objekte ausgibt, die von Control
; Die Rückkehr von List<Button>.GetEnumerator()
wird diese Erwartung erfüllen. Im Gegensatz dazu würde jemand, der List<Int32>
auf List<Object>
umsetzt, jemanden erwarten, der GetEnumerator()
aufruft, würde aber etwas erwarten, das Heap-Objektreferenzen ausgeben würde, aber List<Integer>.GetEnumerator
liefert stattdessen etwas, das Ganzzahlen vom Werttyp ausgibt.
Es ist möglich, Int32
Werte in einem List<Object>
oder einem List<ValueType>
zu speichern; Wenn Sie eine ganze Zahl in eine solche Liste speichern, wird sie in ihr Heap-Objekt konvertiert und ein Verweis darauf gespeichert. Der Aufruf von GetEnumerator()
würde etwas ergeben, das Heap-Referenzen ausgibt. Es gibt jedoch keine Möglichkeit anzugeben, dass eine solche Liste nur Instanzen des Heap-Typs enthält, der Int32
entspricht. In C ++ / CLI ist es möglich, Variablen vom Typ "Verweis auf Heap-gespeicherten Werttyp" zu deklarieren, aber die Mechanismen hinter generischen Typen in .net können nicht mit solchen Typen arbeiten.