Wie leiten sich ValueTypes von Object (ReferenceType) ab und sind dennoch ValueTypes?

83

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?

Joan Venge
quelle
Ergebnis der schwarzen Magie des System.ValueTypeTyps im CLR-Typsystem.
RBT

Antworten:

107

Mit C # können Strukturen nicht von Klassen abgeleitet werden

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:

Leiten sich Strukturen von einem Basistyp ab?

Einfach ja. Wir können dies sehen, indem wir die erste Seite der Spezifikation lesen:

Alle C # -Typen, einschließlich primitiver Typen wie int und double, erben von einem einzelnen Stammobjekttyp.

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.

Gibt es andere Möglichkeiten, wie wir wissen, dass 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.

Darf ich eine benutzerdefinierte Struktur aus einer Klasse meiner Wahl ableiten?

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 sind ValueType. Da diese erforderlich sind , verbietet Ihnen die C # -Sprache die Angabe der Ableitungsbeziehung im Code.

Warum es verbieten?

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 .

Welche Beziehung besteht zwischen Vererbung und Ableitung von einer Klasse ?

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.

Alle vererbbaren Mitglieder? Sicher nicht. Sind private Mitglieder vererbbar?

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:


Wie geht die CLR damit um?

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 leiten sich ValueTypes von Object (ReferenceType) ab und sind dennoch ValueTypes?

wie

Wie ist es möglich, dass sich jede rote Box (Werttypen) in der Box O (System.Object) befindet (von dieser abgeleitet ist), die eine blaue Box (ein Referenztyp) ist und dennoch eine rote Box (ein Werttyp) ist?

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.

Eric Lippert
quelle
8
Sprachkonstrukte sollten sinnvoll sein. Was würde es bedeuten , wenn ein beliebiger Werttyp von einem beliebigen Referenztyp abgeleitet würde? Gibt es etwas, das Sie mit einem solchen Schema erreichen könnten, das Sie nicht auch mit benutzerdefinierten impliziten Konvertierungen erreichen könnten?
Eric Lippert
2
Ich denke nicht. Ich dachte nur, Sie könnten einige Mitglieder für viele Werttypen zur Verfügung haben, die Sie als Gruppe sehen, was Sie tun könnten, indem Sie eine abstrakte Klasse verwenden, um die Struktur abzuleiten. Ich denke, Sie könnten implizite Conversions verwenden, aber dann würden Sie eine Leistungsstrafe zahlen, oder? Wenn Sie Millionen von ihnen machen.
Joan Venge
8
Ah ich sehe. Sie möchten die Vererbung nicht als Mechanismus zum Modellieren von "ist eine Art" Beziehungen verwenden, sondern einfach als Mechanismus zum Teilen von Code zwischen einer Reihe verwandter Typen. Das scheint ein vernünftiges Szenario zu sein, obwohl ich persönlich versuche, die Vererbung nicht nur als Komfort für die gemeinsame Nutzung von Code zu verwenden.
Eric Lippert
3
Joan Um das Verhalten einmal zu definieren, können Sie eine Schnittstelle erstellen, die Strukturen, die Sie freigeben möchten, die Schnittstelle implementieren lassen und dann eine Erweiterungsmethode erstellen, die auf der Schnittstelle ausgeführt wird. Ein mögliches Problem bei diesem Ansatz besteht darin, dass beim Aufrufen der Schnittstellenmethoden die Struktur zuerst eingerahmt und der kopierte Boxwert an die Erweiterungsmethode übergeben wird. Jede Änderung des Status erfolgt auf der Kopie des Objekts, die für Benutzer der API möglicherweise nicht intuitiv ist.
David Silva Smith
2
@Sipo: Um fair zu sein, lautet die Frage: "Wie geht die CLR damit um?" und die Antwort beschreibt gut, wie die CLR diese Regeln implementiert. Aber hier ist die Sache: Wir sollten erwarten, dass das System, das eine Sprache implementiert, nicht die gleichen Regeln wie die Sprache hat! Implementierungssysteme sind notwendigerweise untergeordnete Systeme, aber verwechseln wir nicht die Regeln dieses untergeordneten Systems mit den Regeln des darauf aufbauenden übergeordneten Systems. Sicher, das CLR-Typsystem unterscheidet zwischen Boxed- und Unboxed-Werttypen, wie ich in meiner Antwort festgestellt habe. Aber C # nicht .
Eric Lippert
21

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, ValueTypeist 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.

JaredPar
quelle
+1 für den guten Punkt über Strukturen, die nicht von irgendetwas abgeleitet sind [außer implizit von System.ValueType].
Reed Copsey
2
Sie sagen, das ValueTypeist etwas Besonderes, aber es ist erwähnenswert, dass es ValueTypesich tatsächlich um einen Referenztyp handelt.
LukeH
Wenn es intern möglich ist, dass Strukturen von einer Klasse abgeleitet werden, warum machen sie sie dann nicht für alle verfügbar?
Joan Venge
1
@ Joan: Sie tun es nicht wirklich. Dies dient nur dazu, dass Sie eine Struktur in ein Objekt umwandeln und dort als Dienstprogramm verwenden können. Technisch gesehen werden Werttypen im Vergleich zur Implementierung von Klassen von der CLR jedoch völlig anders behandelt.
Reed Copsey
2
@JoanVenge Ich glaube, die Verwirrung hier besagt, dass Strukturen von der ValueType-Klasse innerhalb der CLR abgeleitet sind. Ich glaube, es ist richtiger zu sagen, dass innerhalb der CLR Strukturen nicht wirklich existieren, die Implementierung von "struct" innerhalb der CLR ist tatsächlich die ValueType-Klasse. Es ist also nicht so, dass eine Struktur von ValueType in der CLR erbt.
Wired_in
20

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).

Reed Copsey
quelle
6

Ihre Aussage ist falsch, daher Ihre Verwirrung. Mit C # können Strukturen von Klassen abgeleitet werden. Alle Strukturen stammen von derselben Klasse, System.ValueType

Versuchen wir also Folgendes:

 struct MyStruct :  System.ValueType
 {
 }

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:

Ich denke, der beste Weg, dies zu beantworten, ist, dass ValueType etwas Besonderes ist. 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.

yi.han
quelle
Dieselben Datenstrukturen werden in .NET verwendet, um den Inhalt von Werttypen und Referenztypen zu beschreiben. Wenn die CLR jedoch eine Typdefinition sieht, die als abgeleitet definiert ist 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 sind System.ValueType, aber C # ...
Supercat
... verbietet die Angabe, dass Strukturen von irgendetwas erben, weil es nur eine Sache gibt, von der sie erben können ( System.ValueType), und verbietet Klassen die Angabe, von der sie erben, System.ValueTypeweil sich jede Klasse, die auf diese Weise deklariert wurde, wie ein Werttyp verhält.
Supercat
3

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.

Superkatze
quelle
1
Ich denke , Sie meinen, „Valuetype ist nicht wirklich der Basistyp von Werttypen“
wired_in
1
@wired_in: Danke. Korrigiert.
Supercat
2

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 und class 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:

.class MyClass
{
}

 

Eine Schnittstelle wird durch eine Klassendefinition mit dem interfacesemantischen Attribut definiert:

.class interface MyInterface
{
}

 

Was ist mit Werttypen?

Der Grund, warum Strukturen von Werttypen erben können System.ValueTypeund 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:

namespace MyNamespace
{
    struct MyValueType : ICloneable
    {
        public int A;
        public int B;
        public int C;

        public object Clone()
        {
            // body omitted
        }
    }
}

Das Folgende ist die IL-Klassendefinition dieser Struktur:

.class MyNamespace.MyValueType extends [mscorlib]System.ValueType implements [mscorlib]System.ICloneable
{
    .field public int32 A;
    .field public int32 B;
    .field public int32 C;

    .method public final hidebysig newslot virtual instance object Clone() cil managed
    {
        // body omitted
    }
}

Also, was ist hier los? Es erweitert sich deutlich System.ValueType, was ein Objekt- / Referenztyp ist, und implementiert System.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örter extendsund implementsgelten nur für den Box-Typ.

Zur Verdeutlichung führt die obige Klassendefinition zwei Dinge aus:

  1. Definiert einen Werttyp mit 3 Feldern (und einer Methode). Es erbt nichts und implementiert keine Schnittstellen (Werttypen können beides nicht).
  2. Definiert einen Objekttyp (den Box-Typ) mit 3 Feldern (und Implementieren einer Schnittstellenmethode), der von System.ValueTypeder System.ICloneableSchnittstelle erbt und diese implementiert .

Beachten Sie auch, dass jede erweiterte Klassendefinition System.ValueTypeauch 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 structSchlü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 classund valuetype. Wenn wir verwenden, geben valuetype MyNamespace.MyTypewir die Wertetypklassendefinition mit dem Namen MyNamespace.MyType an. Ebenso können wir class MyNamespace.MyTypedie 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 in boxedder CLI-Spezifikation angegebene Schlüsselwort tatsächlich implementiert wäre, könnten wir boxed MyNamespace.MyTypeden Box-Typ der Werttyp-Klassendefinition mit dem Namen MyNamespace.MyType angeben.

Nimmt also .method static void Print(valuetype MyNamespace.MyType test) cil managedden Werttyp, der durch eine Werttypklassendefinition mit dem Namen definiert ist MyNamespace.MyType,

while .method static void Print(class MyNamespace.MyType test) cil managednimmt den Objekttyp, der durch die benannte Objekttypklassendefinition definiert ist MyNamespace.MyType.

Wenn boxedes sich um ein Schlüsselwort handelt, wird ebenfalls .method static void Print(boxed MyNamespace.MyType test) cil managedder Box-Typ des Wertetyps verwendet, der durch eine Klassendefinition mit dem Namen definiert ist MyNamespace.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, objectoder boxed MyNamespace.MyValueTypeals 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.ValueTypeoder einem anderen Typ und können keine Schnittstellen implementieren. Die entsprechenden Box- Typen, die ebenfalls definiert sind, erben von Schnittstellen System.ValueTypeund können diese implementieren.

Eine .classDefinition definiert je nach Umstand unterschiedliche Dinge.

  • Wenn das interfacesemantische Attribut angegeben ist, definiert die Klassendefinition eine Schnittstelle.
  • Wenn das interfacesemantische Attribut nicht angegeben ist und die Definition nicht erweitert wird System.ValueType, definiert die Klassendefinition einen Objekttyp (Klasse).
  • Wenn das interfacesemantische Attribut nicht spezifiziert ist, und die Definition nicht erstrecken System.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:

public struct MyStruct
{
    public int A;
    public short B;
    public int C;
}

Wenn wir uns vorstellen, dass eine Instanz von MyStruct unter der Adresse 0x1000 zugewiesen wurde, ist dies das Speicherlayout:

0x1000: int A;
0x1004: short B;
0x1006: 2 byte padding
0x1008: int C;

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:

public class MyClass
{
    public int A;
    public short B;
    public int C;
}

Wenn Sie sich dieselbe Adresse vorstellen, lautet das Speicherlayout wie folgt:

0x1000: Pointer to object header
0x1004: int A;
0x1008: int C;
0x100C: short B;
0x100E: 2 byte padding
0x1010: 4 bytes extra

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 aufzurufen System.Object, muss die Laufzeit die Struktur boxen.

MyStruct myStruct = new MyStruct();
Console.WriteLine(myStruct.ToString()); // ToString() call causes boxing of MyStruct.

Wenn die Struktur jedoch überschrieben wird, ToString()wird der Aufruf statisch gebunden und die Laufzeit wird MyStruct.ToString()ohne Boxen und ohne Suche in virtuellen Methodentabellen aufgerufen (Strukturen haben keine). Aus diesem Grund kann der ToString()Anruf auch inline geschaltet werden.

Wenn die Struktur überschrieben ToString()und eingerahmt ist, wird der Aufruf mithilfe der virtuellen Methodentabelle aufgelöst.

System.ValueType myStruct = new MyStruct(); // Creates a new instance of the boxed type of MyStruct.
Console.WriteLine(myStruct.ToString()); // ToString() is now called through the virtual method table.

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 die ToString()in der Struktur definierte Methode direkt mit dem Box-Typ im thisZeiger aufgerufen würde, würde beim Versuch, auf das Feld Ain MyStructzuzugreifen, 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ührt ToString(). Diese versteckte Methode entpackt (nur Adressberechnung, wie die unboxIL-Anweisung) den Box-Typ und ruft dann statisch die ToString()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

I.8.2.4 Für jeden Werttyp definiert das CTS einen entsprechenden Referenztyp, der als Box-Typ bezeichnet wird. Das Gegenteil ist nicht der Fall: Im Allgemeinen haben Referenztypen keinen entsprechenden Werttyp. Die Darstellung eines Werts eines Box-Typs (eines Box-Werts) ist ein Ort, an dem ein Wert des Werttyps gespeichert werden kann. Ein Box-Typ ist ein Objekttyp und ein Box-Wert ist ein Objekt.

Werttypen definieren

I.8.9.7 Nicht alle durch eine Klassendefinition definierten Typen sind Objekttypen (siehe §I.8.2.3); Insbesondere sind Werttypen keine Objekttypen, sondern werden mithilfe einer Klassendefinition definiert. Eine Klassendefinition für einen Werttyp definiert sowohl den Wertetyp (ohne Box) als auch den zugehörigen Typ mit Box (siehe §I.8.2.4). Die Mitglieder der Klassendefinition definieren die Darstellung von beiden.

II.10.1.3 Die semantischen Attribute des Typs geben an, ob eine Schnittstelle, eine Klasse oder ein Wertetyp definiert werden soll. Das Schnittstellenattribut gibt eine Schnittstelle an. Wenn dieses Attribut nicht vorhanden ist und die Definition (direkt oder indirekt) System.ValueType erweitert und die Definition nicht für System.Enum gilt, muss ein Werttyp definiert werden (§II.13). Andernfalls ist eine Klasse zu definieren (§II.11).

Werttypen erben nicht

I.8.9.10 In ihrer unboxed Form erben Werttypen von keinem Typ. Boxed-Value-Typen erben direkt von System.ValueType, es sei denn, es handelt sich um Aufzählungen. In diesem Fall erben sie von System.Enum. Boxed-Value-Typen müssen versiegelt sein.

II.13 Unboxed-Werttypen werden nicht als Untertypen eines anderen Typs betrachtet, und es ist nicht gültig, die isinst-Anweisung (siehe Partition III) für Unboxed-Werttypen zu verwenden. Die isinst-Anweisung kann jedoch für Boxed-Value-Typen verwendet werden.

I.8.9.10 Ein Werttyp erbt nicht. Vielmehr definiert der in der Klassendefinition angegebene Basistyp den Basistyp des Box-Typs.

Werttypen implementieren keine Schnittstellen

I.8.9.7 Werttypen unterstützen keine Schnittstellenverträge, die zugehörigen Box-Typen jedoch.

II.13 Werttypen müssen null oder mehr Schnittstellen implementieren, dies hat jedoch nur in ihrer Boxform eine Bedeutung (§II.13.3).

I.8.2.4 Schnittstellen und Vererbung werden nur für Referenztypen definiert. Während eine Werttypdefinition (§I.8.9.7) beide Schnittstellen angeben kann, die vom Werttyp und der Klasse (System.ValueType oder System.Enum) implementiert werden sollen, gelten diese nur für Werte in Kästchen .

Das nicht vorhandene Box-Schlüsselwort

II.13.1 Auf die nicht verpackte Form eines Werttyps wird mit dem Schlüsselwort valueetype gefolgt von einer Typreferenz verwiesen. Auf die Boxform eines Werttyps wird mit dem Schlüsselwort boxed gefolgt von einer Typreferenz verwiesen.

Hinweis: Die Angabe ist hier falsch, es gibt kein boxedSchlü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)myStructin C # erstellt eine neue Instanz des Box-Typs des Werttyps; Es werden keine Casts ausgeführt. Ebenso wird (MyStruct)objin C # ein Box-Typ entpackt, der den Wertteil herauskopiert. Es werden keine Casts ausgeführt.

MulleDK19
quelle
1
Endlich eine Antwort, die klar beschreibt, wie es funktioniert! Dieser verdient es, die akzeptierte Antwort zu sein. Gut gemacht!
Just Shadow