In C # können Strukturen nicht von Klassen abgeleitet werden, aber alle ValueTypes werden von Object abgeleitet. Wo wird diese Unterscheidung getroffen?
Wie geht die CLR damit um?
c#
.net
clr
value-type
reference-type
Joan Venge
quelle
quelle
System.ValueType
Typs im CLR-Typsystem.Antworten:
Ihre Aussage ist falsch, daher Ihre Verwirrung. C # tut erlauben structs von Klassen abzuleiten. Alle Strukturen stammen von derselben Klasse, System.ValueType, die von System.Object abgeleitet ist. Und alle Aufzählungen stammen von System.Enum.
UPDATE: In einigen (jetzt gelöschten) Kommentaren gab es einige Verwirrung, die einer Klärung bedarf. Ich werde einige zusätzliche Fragen stellen:
Einfach ja. Wir können dies sehen, indem wir die erste Seite der Spezifikation lesen:
Nun stelle ich fest, dass die Spezifikation den Fall hier überbewertet. Zeigertypen werden nicht vom Objekt abgeleitet, und die Ableitungsbeziehung für Schnittstellentypen und Typparametertypen ist komplexer als in dieser Skizze angegeben. Es ist jedoch klar, dass alle Strukturtypen von einem Basistyp abgeleitet sind.
Sicher. Ein Strukturtyp kann überschreiben
ToString
. Was überschreibt es, wenn nicht eine virtuelle Methode ihres Basistyps? Daher muss es einen Basistyp haben. Dieser Basistyp ist eine Klasse.Einfach nein. Dies bedeutet nicht, dass Strukturen nicht von einer Klasse abgeleitet sind . Strukturen leiten sich von einer Klasse ab und erben dadurch die vererbbaren Mitglieder dieser Klasse. In der Tat ist structs erforderlich herzuleiten aus einer bestimmten Klasse: Aufzählungen von abzuleiten sind erforderlich
Enum
, Strukturen zu von derive erforderlich sindValueType
. Da diese erforderlich sind , verbietet Ihnen die C # -Sprache die Angabe der Ableitungsbeziehung im Code.Wenn eine Beziehung erforderlich ist , hat der Sprachdesigner folgende Optionen: (1) Der Benutzer muss die erforderliche Beschwörung eingeben, (2) sie optional machen oder (3) sie verbieten. Jedes hat Vor- und Nachteile, und die C # -Sprachendesigner haben je nach den jeweiligen Details unterschiedliche Entscheidungen getroffen.
Zum Beispiel müssen const-Felder statisch sein, aber es ist verboten zu sagen, dass dies der Fall ist, da dies erstens eine sinnlose Aussprache ist und zweitens impliziert, dass es nicht statische const-Felder gibt. Überladene Operatoren müssen jedoch als statisch markiert werden, obwohl der Entwickler keine andere Wahl hat. Für Entwickler ist es zu einfach zu glauben, dass eine Operatorüberladung ansonsten eine Instanzmethode ist. Dies überschreibt die Sorge, dass ein Benutzer glauben könnte, dass "statisch" impliziert, dass beispielsweise "virtuell" auch eine Möglichkeit ist.
In diesem Fall dass ein Benutzer sagen , dass ihre Struktur ergibt sich aus Valuetype scheint wie nur überschüssige Wortschwall, und es bedeutet , dass die Struktur könnte von einem anderen Typ abzuleiten. Um diese beiden Probleme zu beseitigen, macht es C # illegal , im Code anzugeben, dass eine Struktur von einem Basistyp abgeleitet ist, obwohl dies eindeutig der Fall ist.
Ebenso leiten sich alle Delegatentypen ab
MulticastDelegate
, aber C # erfordert, dass Sie das nicht sagen.Nun haben wir festgestellt, dass alle Strukturen in C # von einer Klasse abgeleitet sind .
Viele Menschen sind durch die Vererbungsbeziehung in C # verwirrt. Die Vererbungsbeziehung ist recht einfach: Wenn eine Struktur, eine Klasse oder ein Delegat vom Typ D von einem Klassentyp B abgeleitet ist, sind die vererbbaren Mitglieder von B auch Mitglieder von D. So einfach ist das.
Was bedeutet es in Bezug auf die Vererbung, wenn wir sagen, dass eine Struktur von ValueType abgeleitet ist? Einfach, dass alle vererbbaren Mitglieder von ValueType auch Mitglieder der Struktur sind. Auf diese Weise erhalten Strukturen beispielsweise ihre Implementierung von
ToString
; Es wird von der Basisklasse der Struktur geerbt.Ja. Alle privaten Mitglieder einer Basisklasse sind auch Mitglieder des abgeleiteten Typs. Es ist natürlich illegal, diese Mitglieder beim Namen anzurufen, wenn sich die Anrufstelle nicht in der Eingabehilfen-Domäne des Mitglieds befindet. Nur weil Sie ein Mitglied haben, heißt das nicht, dass Sie es verwenden können!
Wir fahren nun mit der ursprünglichen Antwort fort:
Sehr gut. :-)
Was einen Werttyp zu einem Werttyp macht, ist, dass seine Instanzen nach Wert kopiert werden . Was einen Referenztyp zu einem Referenztyp macht, ist, dass seine Instanzen durch Referenz kopiert werden . Sie scheinen der Ansicht zu sein, dass die Vererbungsbeziehung zwischen Werttypen und Referenztypen irgendwie besonders und ungewöhnlich ist, aber ich verstehe nicht, was dieser Glaube ist. Vererbung hat nichts damit zu tun, wie Dinge kopiert werden.
Schau es dir so an. Angenommen, ich habe Ihnen die folgenden Fakten mitgeteilt:
Es gibt zwei Arten von Boxen, rote Boxen und blaue Boxen.
Jedes rote Kästchen ist leer.
Es gibt drei spezielle blaue Kästchen mit den Namen O, V und E.
O ist in keiner Box.
V ist in O.
E ist in V.
Keine andere blaue Box befindet sich in V.
In E. befindet sich keine blaue Box.
Jedes rote Kästchen ist entweder in V oder E.
Jede andere blaue Box als O befindet sich in einer blauen Box.
Die blauen Kästchen sind Referenztypen, die roten Kästchen sind Werttypen, O ist System.Object, V ist System.ValueType, E ist System.Enum und die "innere" Beziehung ist "abgeleitet von".
Das ist ein perfekt konsistentes und unkompliziertes Regelwerk, das Sie leicht selbst umsetzen können, wenn Sie viel Pappe und viel Geduld haben. Ob eine Box rot oder blau ist, hat nichts mit dem zu tun, was sich darin befindet. In der realen Welt ist es durchaus möglich, eine rote Box in eine blaue Box zu stecken. In der CLR ist es völlig legal, einen Werttyp zu erstellen, der von einem Referenztyp erbt, sofern es sich entweder um System.ValueType oder System.Enum handelt.
Lassen Sie uns also Ihre Frage umformulieren:
wie
Wenn Sie es so formulieren, hoffe ich, dass es offensichtlich ist. Nichts hindert Sie daran, eine rote Box in Box V zu stecken, die sich in Box O befindet, die blau ist. Warum sollte es das geben?
EIN ZUSÄTZLICHES UPDATE:
Joans ursprüngliche Frage war, wie es möglich istdass ein Werttyp von einem Referenztyp abgeleitet ist. Meine ursprüngliche Antwort erklärte keinen der Mechanismen, die die CLR verwendet, um die Tatsache zu erklären, dass wir eine Ableitungsbeziehung zwischen zwei Dingen haben, die völlig unterschiedliche Darstellungen haben - nämlich ob die referenzierten Daten einen Objektheader haben, a Synchronisierungsblock, ob er einen eigenen Speicher zum Zwecke der Speicherbereinigung besitzt, und so weiter. Diese Mechanismen sind kompliziert, zu kompliziert, um sie in einer Antwort zu erklären. Die Regeln des CLR-Typsystems sind etwas komplexer als die etwas vereinfachte Variante, die wir in C # sehen, wo beispielsweise nicht stark zwischen der Box- und der Unbox-Version eines Typs unterschieden wird. Die Einführung von Generika führte auch dazu, dass der CLR eine zusätzliche Komplexität hinzugefügt wurde.
quelle
Kleine Korrektur, C # erlaubt nicht, dass Strukturen benutzerdefiniert von irgendetwas abgeleitet werden, nicht nur von Klassen. Eine Struktur kann lediglich eine Schnittstelle implementieren, die sich stark von der Ableitung unterscheidet.
Ich denke, der beste Weg, dies zu beantworten,
ValueType
ist etwas Besonderes. Es ist im Wesentlichen die Basisklasse für alle Werttypen im CLR-Typsystem. Es ist schwer zu beantworten, wie "die CLR damit umgeht", da dies einfach eine Regel der CLR ist.quelle
ValueType
ist etwas Besonderes, aber es ist erwähnenswert, dass esValueType
sich tatsächlich um einen Referenztyp handelt.Dies ist ein etwas künstliches Konstrukt, das von der CLR verwaltet wird, damit alle Typen als System.Object behandelt werden können.
Werttypen werden von System.Object über System.ValueType abgeleitet . Hier erfolgt die spezielle Behandlung (dh die CLR behandelt das Boxen / Entpacken usw. für alle von ValueType abgeleiteten Typen).
quelle
Versuchen wir also Folgendes:
Dies wird nicht einmal kompiliert. Der Compiler erinnert Sie daran, dass "Geben Sie 'System.ValueType' in die Schnittstellenliste keine Schnittstelle ein".
Wenn Sie Int32 dekompilieren, das eine Struktur ist, finden Sie:
public struct Int32: IComparable, IFormattable, IConvertible {}, ohne zu erwähnen, dass es von System.ValueType abgeleitet ist. Im Objektbrowser erbt Int32 jedoch von System.ValueType.
All dies lässt mich glauben:
quelle
ValueType
, verwendet sie diese, um zwei Arten von Objekten zu definieren: einen Heap-Objekttyp, der verhält sich wie ein Referenztyp und ein Speicherorttyp, der sich effektiv außerhalb des Typvererbungssystems befindet. Da diese beiden Arten von Dingen in sich gegenseitig ausschließenden Kontexten verwendet werden, können für beide dieselben Typdeskriptoren verwendet werden. Auf der CLR-Ebene wird eine Struktur als Klasse definiert, deren Eltern übergeordnet sindSystem.ValueType
, aber C # ...System.ValueType
), und verbietet Klassen die Angabe, von der sie erben,System.ValueType
weil sich jede Klasse, die auf diese Weise deklariert wurde, wie ein Werttyp verhält.Ein Boxed-Value-Typ ist effektiv ein Referenztyp (er läuft wie einer und quakt wie einer, also ist er effektiv einer). Ich würde vorschlagen, dass ValueType nicht wirklich der Basistyp von Werttypen ist, sondern vielmehr der Basisreferenztyp, in den Werttypen konvertiert werden können, wenn sie in den Typ Object umgewandelt werden. Werttypen ohne Box selbst befinden sich außerhalb der Objekthierarchie.
quelle
Begründung
Von allen Antworten kommt die Antwort von @ supercat der tatsächlichen Antwort am nächsten. Da die anderen Antworten die Frage nicht wirklich beantworten und geradezu falsche Behauptungen aufstellen (zum Beispiel, dass Werttypen von irgendetwas erben), habe ich beschlossen, die Frage zu beantworten.
Prolog
Diese Antwort basiert auf meinem eigenen Reverse Engineering und der CLI-Spezifikation.
struct
undclass
sind C # -Schlüsselwörter. In Bezug auf die CLI werden alle Typen (Klassen, Schnittstellen, Strukturen usw.) durch Klassendefinitionen definiert.Beispielsweise ist ein Objekttyp (in C # als bekannt
class
) wie folgt definiert:Eine Schnittstelle wird durch eine Klassendefinition mit dem
interface
semantischen Attribut definiert:Was ist mit Werttypen?
Der Grund, warum Strukturen von Werttypen erben können
System.ValueType
und immer noch sind, liegt darin, dass sie dies nicht tun.Werttypen sind einfache Datenstrukturen. Werttypen haben nicht erben nichts und sie können nicht Schnittstellen implementieren. Werttypen sind keine Untertypen und haben keine Typinformationen. Bei einer Speicheradresse eines Werttyps ist es nicht möglich zu identifizieren, was der Werttyp darstellt, im Gegensatz zu einem Referenztyp, der Typinformationen in einem ausgeblendeten Feld enthält.
Wenn wir uns folgende C # -Struktur vorstellen:
Das Folgende ist die IL-Klassendefinition dieser Struktur:
Also, was ist hier los? Es erweitert sich deutlich
System.ValueType
, was ein Objekt- / Referenztyp ist, und implementiertSystem.ICloneable
.Die Erklärung ist, dass, wenn eine Klassendefinition erweitert wird
System.ValueType
tatsächlich zwei Dinge definiert: einen Werttyp und den entsprechenden Box-Typ des Werttyps. Die Mitglieder der Klassendefinition definieren die Darstellung sowohl für den Werttyp als auch für den entsprechenden Box-Typ. Es ist nicht der Werttyp, der erweitert und implementiert wird, sondern der entsprechende Box-Typ, der dies tut. Die Schlüsselwörterextends
undimplements
gelten nur für den Box-Typ.Zur Verdeutlichung führt die obige Klassendefinition zwei Dinge aus:
System.ValueType
derSystem.ICloneable
Schnittstelle erbt und diese implementiert .Beachten Sie auch, dass jede erweiterte Klassendefinition
System.ValueType
auch intrinsisch versiegelt ist, unabhängig davon, ob diesealed
Schlüsselwort angegeben ist oder nicht.Da Werttypen nur einfache Strukturen sind, nicht erben, nicht implementieren und keinen Polymorphismus unterstützen, können sie nicht mit dem Rest des Typsystems verwendet werden. Um dies zu umgehen, definiert die CLR zusätzlich zum Werttyp einen entsprechenden Referenztyp mit denselben Feldern, den so genannten Box-Typ. Während ein Werttyp nicht an Methoden weitergegeben werden kann, die ein nehmen
object
, kann der entsprechende Box-Typ dies .Nun, wenn Sie eine Methode in C # wie definieren würden
public static void BlaBla(MyNamespace.MyValueType x)
,Sie wissen, dass die Methode den Werttyp annimmt
MyNamespace.MyValueType
.Oben haben wir erfahren, dass die Klassendefinition, die sich aus dem
struct
Schlüsselwort in C # ergibt, tatsächlich sowohl einen Werttyp als auch einen Objekttyp definiert. Wir können uns jedoch nur auf den definierten Wertetyp beziehen. Obwohl in der CLI-Spezifikation angegeben ist, dass das Constraint-Schlüsselwortboxed
verwendet werden kann, um auf eine Box-Version eines Typs zu verweisen, ist dieses Schlüsselwort nicht vorhanden (siehe ECMA-335, II.13.1 Referenzieren von Werttypen). Aber stellen wir uns vor, dass es für einen Moment so ist.Wenn auf Typen in IL verwiesen wird, werden einige Einschränkungen unterstützt, darunter
class
undvaluetype
. Wenn wir verwenden, gebenvaluetype MyNamespace.MyType
wir die Wertetypklassendefinition mit dem Namen MyNamespace.MyType an. Ebenso können wirclass MyNamespace.MyType
die Objekttypklassendefinition mit dem Namen MyNamespace.MyType angeben. Dies bedeutet, dass Sie in IL einen Werttyp (Struktur) und einen Objekttyp (Klasse) mit demselben Namen haben und diese dennoch unterscheiden können. Wenn das inboxed
der CLI-Spezifikation angegebene Schlüsselwort tatsächlich implementiert wäre, könnten wirboxed MyNamespace.MyType
den Box-Typ der Werttyp-Klassendefinition mit dem Namen MyNamespace.MyType angeben.Nimmt also
.method static void Print(valuetype MyNamespace.MyType test) cil managed
den Werttyp, der durch eine Werttypklassendefinition mit dem Namen definiert istMyNamespace.MyType
,while
.method static void Print(class MyNamespace.MyType test) cil managed
nimmt den Objekttyp, der durch die benannte Objekttypklassendefinition definiert istMyNamespace.MyType
.Wenn
boxed
es sich um ein Schlüsselwort handelt, wird ebenfalls.method static void Print(boxed MyNamespace.MyType test) cil managed
der Box-Typ des Wertetyps verwendet, der durch eine Klassendefinition mit dem Namen definiert istMyNamespace.MyType
.Sie würden dann in der Lage sein , die Box - Typ wie jeder andere Objekttyp instanziiert und es um auf jede Methode übergeben , die eine nimmt
System.ValueType
,object
oderboxed MyNamespace.MyValueType
als Argument, und es wäre für alle Absichten und Zwecke, Arbeit wie jede andere Referenztyp. Es ist KEIN Werttyp, sondern der entsprechende Box-Typ eines Werttyps.Zusammenfassung
Also, zusammenfassend und um die Frage zu beantworten:
Werttypen sind keine Referenztypen und erben nicht von
System.ValueType
oder einem anderen Typ und können keine Schnittstellen implementieren. Die entsprechenden Box- Typen, die ebenfalls definiert sind, erben von SchnittstellenSystem.ValueType
und können diese implementieren.Eine
.class
Definition definiert je nach Umstand unterschiedliche Dinge.interface
semantische Attribut angegeben ist, definiert die Klassendefinition eine Schnittstelle.interface
semantische Attribut nicht angegeben ist und die Definition nicht erweitert wirdSystem.ValueType
, definiert die Klassendefinition einen Objekttyp (Klasse).interface
semantische Attribut nicht spezifiziert ist, und die Definition nicht erstreckenSystem.ValueType
, definiert die Klassendefinition einen Werttyp und seinen entsprechenden geschachtelte Typen (struct).Speicherlayout
In diesem Abschnitt wird ein 32-Bit-Prozess angenommen
Wie bereits erwähnt, haben Werttypen keine Typinformationen, und daher ist es nicht möglich, anhand seines Speicherorts zu identifizieren, was ein Werttyp darstellt. Eine Struktur beschreibt einen einfachen Datentyp und enthält nur die Felder, die sie definiert:
Wenn wir uns vorstellen, dass eine Instanz von MyStruct unter der Adresse 0x1000 zugewiesen wurde, ist dies das Speicherlayout:
Strukturen verwenden standardmäßig ein sequentielles Layout. Felder werden an Grenzen ihrer eigenen Größe ausgerichtet. Um dies zu befriedigen, wird eine Polsterung hinzugefügt.
Wenn wir eine Klasse genauso definieren wie:
Wenn Sie sich dieselbe Adresse vorstellen, lautet das Speicherlayout wie folgt:
Klassen verwenden standardmäßig das automatische Layout, und der JIT-Compiler ordnet sie in der optimalen Reihenfolge an. Felder werden an Grenzen ihrer eigenen Größe ausgerichtet. Um dies zu befriedigen, wird eine Polsterung hinzugefügt. Ich bin mir nicht sicher warum, aber jede Klasse hat am Ende immer zusätzliche 4 Bytes.
Offset 0 enthält die Adresse des Objektheaders, der Typinformationen, die virtuelle Methodentabelle usw. enthält. Dadurch kann die Laufzeit im Gegensatz zu Werttypen identifizieren, was die Daten an einer Adresse darstellen.
Werttypen unterstützen daher weder Vererbung, Schnittstellen noch Polymorphismus.
Methoden
Werttypen haben keine virtuellen Methodentabellen und unterstützen daher keinen Polymorphismus. Der entsprechende Box-Typ tut dies jedoch .
Wenn Sie eine Instanz einer Struktur haben und versuchen, eine virtuelle Methode wie in
ToString()
definiert aufzurufenSystem.Object
, muss die Laufzeit die Struktur boxen.Wenn die Struktur jedoch überschrieben wird,
ToString()
wird der Aufruf statisch gebunden und die Laufzeit wirdMyStruct.ToString()
ohne Boxen und ohne Suche in virtuellen Methodentabellen aufgerufen (Strukturen haben keine). Aus diesem Grund kann derToString()
Anruf auch inline geschaltet werden.Wenn die Struktur überschrieben
ToString()
und eingerahmt ist, wird der Aufruf mithilfe der virtuellen Methodentabelle aufgelöst.ToString()
Denken Sie jedoch daran, dass dies in der Struktur definiert ist und daher mit dem Strukturwert arbeitet, sodass ein Werttyp erwartet wird. Der Box-Typ hat wie jede andere Klasse einen Objektheader. Wenn dieToString()
in der Struktur definierte Methode direkt mit dem Box-Typ imthis
Zeiger aufgerufen würde, würde beim Versuch, auf das FeldA
inMyStruct
zuzugreifen, auf den Offset 0 zugegriffen, der im Box-Typ der Objekt-Header-Zeiger wäre. Der Boxed-Typ verfügt also über eine versteckte Methode, die das eigentliche Überschreiben ausführtToString()
. Diese versteckte Methode entpackt (nur Adressberechnung, wie dieunbox
IL-Anweisung) den Box-Typ und ruft dann statisch dieToString()
in der Struktur definierten auf.Ebenso verfügt der Boxed-Typ über eine versteckte Methode für jede implementierte Schnittstellenmethode, die dasselbe Unboxing ausführt und dann die in der Struktur definierte Methode statisch aufruft.
CLI-Spezifikation
Boxen
Werttypen definieren
Werttypen erben nicht
Werttypen implementieren keine Schnittstellen
Das nicht vorhandene Box-Schlüsselwort
Hinweis: Die Angabe ist hier falsch, es gibt kein
boxed
Schlüsselwort.Epilog
Ich denke, ein Teil der Verwirrung darüber, wie Werttypen zu erben scheinen, beruht auf der Tatsache, dass C # Casting-Syntax verwendet, um Boxen und Unboxing durchzuführen, was den Anschein erweckt, als würden Sie Casts ausführen, was nicht wirklich der Fall ist (obwohl das CLR löst eine InvalidCastException aus, wenn versucht wird, den falschen Typ zu entpacken.
(object)myStruct
in C # erstellt eine neue Instanz des Box-Typs des Werttyps; Es werden keine Casts ausgeführt. Ebenso wird(MyStruct)obj
in C # ein Box-Typ entpackt, der den Wertteil herauskopiert. Es werden keine Casts ausgeführt.quelle