IEnumerable<T>
ist eine Co-Variante, unterstützt jedoch keinen Werttyp, sondern nur einen Referenztyp. Der folgende einfache Code wurde erfolgreich kompiliert:
IEnumerable<string> strList = new List<string>();
IEnumerable<object> objList = strList;
Beim Wechsel von string
zu int
wird jedoch ein kompilierter Fehler angezeigt:
IEnumerable<int> intList = new List<int>();
IEnumerable<object> objList = intList;
Der Grund wird in MSDN erklärt :
Die Abweichung gilt nur für Referenztypen. Wenn Sie einen Werttyp für einen Variantentypparameter angeben, ist dieser Typparameter für den resultierenden konstruierten Typ unveränderlich.
Ich habe gesucht und festgestellt, dass einige Fragen den Grund für das Boxen zwischen Werttyp und Referenztyp genannt haben . Aber es klärt mich immer noch nicht auf, warum Boxen der Grund ist?
Könnte jemand bitte eine einfache und detaillierte Erklärung geben, warum Kovarianz und Kontravarianz den Werttyp nicht unterstützen und wie sich das Boxen darauf auswirkt?
quelle
Antworten:
Grundsätzlich gilt Varianz, wenn die CLR sicherstellen kann, dass keine repräsentativen Änderungen an den Werten vorgenommen werden müssen. Referenzen sehen alle gleich aus - Sie können also eine
IEnumerable<string>
alsIEnumerable<object>
ohne Änderung der Darstellung verwenden. Der native Code selbst muss nicht wissen, was Sie mit den Werten tun, solange die Infrastruktur garantiert hat, dass er definitiv gültig ist.Bei Werttypen funktioniert das nicht - um einen
IEnumerable<int>
als zu behandelnIEnumerable<object>
, müsste der Code, der die Sequenz verwendet, wissen, ob eine Boxkonvertierung durchgeführt werden soll oder nicht.Vielleicht möchten Sie Eric Lipperts Blog-Beitrag über Repräsentation und Identität lesen um mehr über dieses Thema im Allgemeinen zu erfahren.
EDIT: Nachdem ich Erics Blog-Post selbst noch einmal gelesen habe, geht es mindestens genauso um Identität wie um Repräsentation, obwohl die beiden miteinander verbunden sind. Bestimmtes:
quelle
int
es sich nicht um einen Subtyp von handeltobject
. Die Tatsache, dass ein Repräsentationswechsel erforderlich ist, ist nur eine Folge davon.Int32
Werttyp, z. B. hat zwei Darstellungsformen, "boxed" und "unboxed". Der Compiler muss Code einfügen, um von einem Formular in das andere zu konvertieren, obwohl dies normalerweise auf Quellcodeebene unsichtbar ist. Tatsächlich wird vom zugrunde liegenden System nur das "Boxed" -Formular als Subtyp betrachtetobject
, aber der Compiler behandelt dies automatisch, wenn ein Werttyp einer kompatiblen Schnittstelle oder einem Typ zugewiesen wirdobject
.Es ist vielleicht einfacher zu verstehen, wenn Sie über die zugrunde liegende Darstellung nachdenken (obwohl dies wirklich ein Implementierungsdetail ist). Hier ist eine Sammlung von Zeichenfolgen:
Sie können sich
strings
die folgende Darstellung vorstellen:Es ist eine Sammlung von drei Elementen, die jeweils auf eine Zeichenfolge verweisen. Sie können dies in eine Sammlung von Objekten umwandeln:
Im Grunde ist es die gleiche Darstellung, außer dass die Referenzen jetzt Objektreferenzen sind:
Die Darstellung ist die gleiche. Die Referenzen werden nur unterschiedlich behandelt; Sie können nicht mehr auf die
string.Length
Unterkunft zugreifen, aber Sie können trotzdem anrufenobject.GetHashCode()
. Vergleichen Sie dies mit einer Sammlung von Ints:Um dies in ein zu konvertieren, müssen
IEnumerable<object>
die Daten durch Boxen der Ints konvertiert werden:Diese Konvertierung erfordert mehr als eine Besetzung.
quelle
this
auf eine Struktur verwiesen wird, deren Felder die des Heap-Objekts überlagern, in dem sie gespeichert sind, anstatt auf das Objekt, das sie enthält. Es gibt keine saubere Möglichkeit für eine Instanz vom Typ Boxed Value, einen Verweis auf das einschließende Heap-Objekt abzurufen.Ich denke, alles beginnt mit der Definition von
LSP
(Liskov-Substitutionsprinzip), die folgende Bedingungen erfüllt:Werttypen können beispielsweise
int
nicht durchobject
in ersetzt werdenC#
. Beweisen ist sehr einfach:Dies gibt
false
auch dann zurück , wenn wir dem Objekt dieselbe "Referenz" zuweisen .quelle
int
ist kein Subtyp von,object
daher gilt das Prinzip nicht. Ihr "Beweis" beruht auf einer ZwischendarstellungInteger
, die ein Subtyp von istobject
und für die die Sprache eine implizite Konvertierung hat (object obj1=myInt;
wird tatsächlich erweitertobject obj1=new Integer(myInt)
;).int
ist kein Subtyp vonobject
. Außerdem ist LSP gelten nicht damyInt
,obj1
undobj2
beziehen sich auf drei verschiedene Objekte: einint
und zwei (versteckt)Integer
s.int
Schlüsselwort von C # ist ein Alias für die BCLsSystem.Int32
, der tatsächlich ein Subtyp vonobject
(ein Alias vonSystem.Object
) ist. Tatsächlichint
istSystem.ValueType
die Basisklasse, wer die Basisklasse istSystem.Object
. Versuchen Sie, den folgenden Ausdruck zu bewerten, und sehen Sie :typeof(int).BaseType.BaseType
. Der Grund, warumReferenceEquals
hier false zurückgegeben wird, ist, dass dieint
Box in zwei separate Boxen unterteilt ist und die Identität jeder Box für jede andere Box unterschiedlich ist. Somit ergeben zwei Boxvorgänge immer zwei Objekte, die unabhängig vom Boxwert niemals identisch sind.System.Int32
oderList<String>.Enumerator
) repräsentiert tatsächlich zwei Arten von Dingen: einen Speicherorttyp und einen Heap-Objekttyp (manchmal als "Boxed-Value-Typ" bezeichnet). Speicherorte, von denen die Typen abgeleitetSystem.ValueType
sind, enthalten die ersteren. Heap-Objekte, deren Typen dies ebenfalls tun, halten letztere. In den meisten Sprachen gibt es eine erweiterte Besetzung von der ersteren und eine sich verengende Besetzung von der letzteren zur ersteren. Beachten Sie, dass Wertetypen inEs kommt auf ein Implementierungsdetail an: Werttypen werden anders als Referenztypen implementiert.
Wenn Sie erzwingen, dass Werttypen als Referenztypen behandelt werden (z. B. Boxen, z. B. indem Sie über eine Schnittstelle auf sie verweisen), können Sie Abweichungen erhalten.
Der einfachste Weg, um den Unterschied zu erkennen, besteht darin, einfach Folgendes zu betrachten
Array
: Ein Array von Werttypen wird zusammenhängend (direkt) im Speicher zusammengestellt, wobei ein Array von Referenztypen nur die Referenz (einen Zeiger) zusammenhängend im Speicher hat; Die Objekte, auf die verwiesen wird, werden separat zugeordnet.Das andere (verwandte) Problem (*) ist, dass (fast) alle Referenztypen für Varianzzwecke dieselbe Darstellung haben und viel Code den Unterschied zwischen den Typen nicht kennen muss, so dass eine Co- und Kontra-Varianz möglich (und leicht) ist implementiert - oft nur durch Weglassen einer zusätzlichen Typprüfung).
(*) Es kann das gleiche Problem gesehen werden ...
quelle