Ich weiß, dass Strukturen in .NET keine Vererbung unterstützen, aber es ist nicht genau klar, warum sie auf diese Weise eingeschränkt sind.
Welcher technische Grund verhindert, dass Strukturen von anderen Strukturen erben?
.net
inheritance
struct
Julia
quelle
quelle
Antworten:
Der Grund, warum Werttypen die Vererbung nicht unterstützen können, liegt an Arrays.
Das Problem ist, dass aus Leistungs- und GC-Gründen Arrays von Werttypen "inline" gespeichert werden. Beispielsweise gegeben
new FooType[10] {...}
, wennFooType
ein Referenztyp ist, werden 11 Objekte auf dem verwalteten Heap (eine für die Anordnung, und 10 für jede Instanz) angelegt werden. WennFooType
es sich stattdessen um einen Werttyp handelt, wird nur eine Instanz auf dem verwalteten Heap erstellt - für das Array selbst (da jeder Array-Wert "inline" mit dem Array gespeichert wird).Nehmen wir nun an, wir hätten Vererbung mit Werttypen. In Kombination mit dem oben beschriebenen "Inline-Speicher" -Verhalten von Arrays passieren schlechte Dinge, wie in C ++ zu sehen ist .
Betrachten Sie diesen Pseudo-C # -Code:
Nach normalen Konvertierungsregeln kann a
Derived[]
in a konvertiert werdenBase[]
(zum Guten oder Schlechten). Wenn Sie also für das obige Beispiel s / struct / class / g verwenden, wird es ohne Probleme kompiliert und wie erwartet ausgeführt. Aber wennBase
undDerived
Werttypen sind und Arrays Werte inline speichern, haben wir ein Problem.Wir haben ein Problem, weil wir
Square()
nichts darüber wissenDerived
. Es wird nur Zeigerarithmetik verwendet, um auf jedes Element des Arrays zuzugreifen, und um einen konstanten Betrag erhöht (sizeof(A)
). Die Versammlung wäre vage wie folgt:(Ja, das ist eine abscheuliche Assembly, aber der Punkt ist, dass wir das Array mit bekannten Konstanten zur Kompilierungszeit inkrementieren, ohne zu wissen, dass ein abgeleiteter Typ verwendet wird.)
Wenn dies tatsächlich passieren würde, hätten wir Probleme mit der Speicherbeschädigung. Insbesondere innerhalb
Square()
,values[1].A*=2
wäre eigentlich sein modifizierendevalues[0].B
!Versuchen Sie, DAS zu debuggen !
quelle
Stellen Sie sich vor, Strukturen unterstützen die Vererbung. Dann erklären:
würde bedeuten, dass Strukturvariablen keine feste Größe haben, und deshalb haben wir Referenztypen.
Noch besser, bedenken Sie Folgendes:
quelle
Foo
erbenBar
soll nicht zulassen , dassFoo
auf eine zugeordnet werdenBar
, sondern eine Struktur erklärt , dass Art und Weise ein paar nützlichen Effekte ermöglichen: (1) ein vom Typ erstellen speziell benannte MitgliedBar
als erstes Element inFoo
und hatFoo
umfasst Mitgliedsnamen, die diesen Mitgliedern einen Alias gebenBar
, sodass Code, der früherBar
angepasst wurde,Foo
stattdessen verwendet werden kann, ohne dass alle Verweise aufthing.BarMember
durch ersetzt werden müssenthing.theBar.BarMember
, und die Fähigkeit erhalten bleibt, alleBar
Felder als Gruppe zu lesen und zu schreiben ; ...Strukturen verwenden keine Referenzen (es sei denn, sie sind eingerahmt, aber Sie sollten versuchen, dies zu vermeiden), daher ist Polymorphismus nicht sinnvoll, da keine Indirektion über einen Referenzzeiger erfolgt. Objekte befinden sich normalerweise auf dem Heap und werden über Referenzzeiger referenziert. Strukturen werden jedoch auf dem Stapel zugewiesen (sofern sie nicht eingerahmt sind) oder "innerhalb" des Speichers zugewiesen, der von einem Referenztyp auf dem Heap belegt wird.
quelle
Foo
dass ein Bereich des Strukturtypen hat inBar
der Lage zu betrachtenBar
‚s Mitglieder als seine eigenen, so dass EinePoint3d
Klasse könnte z. B. ein kapselnPoint2d xy
, sich aber auf dasX
dieses Feldes entwederxy.X
oder beziehenX
.Hier ist , was die docs sagen:
Grundsätzlich sollen sie einfache Daten enthalten und haben daher keine "zusätzlichen Funktionen" wie Vererbung. Es wäre wahrscheinlich technisch möglich, dass sie eine begrenzte Art von Vererbung unterstützen (kein Polymorphismus, da sie sich auf dem Stapel befinden), aber ich glaube, es ist auch eine Entwurfsentscheidung, Vererbung nicht zu unterstützen (wie viele andere Dinge in .NET Sprachen sind.)
Auf der anderen Seite stimme ich den Vorteilen der Vererbung zu, und ich denke, wir haben alle den Punkt erreicht, an dem wir wollen
struct
, dass wir von einem anderen erben, und erkennen, dass dies nicht möglich ist. Aber zu diesem Zeitpunkt ist die Datenstruktur wahrscheinlich so weit fortgeschritten, dass es sowieso eine Klasse sein sollte.quelle
Point3D
aus einer zu erstellenPoint2D
; Sie wären nicht in der Lage um aPoint3D
anstelle von a zu verwendenPoint2D
, aber Sie müssten das nichtPoint3D
komplett neu implementieren .) So habe ich es sowieso interpretiert ...class
über ,struct
wenn angemessen.Eine klassenähnliche Vererbung ist nicht möglich, da eine Struktur direkt auf den Stapel gelegt wird. Eine erbende Struktur wäre größer als die übergeordnete, aber die JIT weiß es nicht und versucht, zu viel auf zu wenig Speicherplatz zu setzen. Klingt etwas unklar, schreiben wir ein Beispiel:
Wenn dies möglich wäre, würde es auf dem folgenden Snippet abstürzen:
Platz wird für die Größe von A zugewiesen, nicht für die Größe von B.
quelle
Es gibt einen Punkt, den ich korrigieren möchte. Obwohl der Grund, warum Strukturen nicht vererbt werden können, darin besteht, dass sie auf dem Stapel leben, ist dies die richtige Erklärung. Structs, wie jedes andere Werttyp kann in dem Stapel leben. Da es davon abhängt, wo die Variable deklariert ist, befinden sie sich entweder im Stapel oder im Heap . Dies ist der Fall, wenn es sich um lokale Variablen bzw. Instanzfelder handelt.
Mit diesen Worten hat Cecil einen Namen richtig getroffen.
Ich möchte dies betonen, Werttypen können auf dem Stapel leben. Das bedeutet nicht, dass sie es immer tun. Lokale Variablen, einschließlich Methodenparameter, werden. Alle anderen werden nicht. Trotzdem bleibt es der Grund, warum sie nicht vererbt werden können. :-)
quelle
Strukturen werden auf dem Stapel zugeordnet. Dies bedeutet, dass die Wertesemantik ziemlich kostenlos ist und der Zugriff auf Strukturelemente sehr billig ist. Dies verhindert keinen Polymorphismus.
Sie können jede Struktur mit einem Zeiger auf ihre virtuelle Funktionstabelle beginnen lassen. Dies wäre ein Leistungsproblem (jede Struktur hätte mindestens die Größe eines Zeigers), aber es ist machbar. Dies würde virtuelle Funktionen ermöglichen.
Was ist mit dem Hinzufügen von Feldern?
Wenn Sie eine Struktur auf dem Stapel zuweisen, weisen Sie eine bestimmte Menge an Speicherplatz zu. Der erforderliche Speicherplatz wird zur Kompilierungszeit festgelegt (ob vorzeitig oder beim JITting). Wenn Sie Felder hinzufügen und dann einem Basistyp zuweisen:
Dadurch wird ein unbekannter Teil des Stapels überschrieben.
Die Alternative besteht darin, dass die Laufzeit dies verhindert, indem nur die Größe von (A) Bytes in eine A-Variable geschrieben wird.
Was passiert, wenn B eine Methode in A überschreibt und auf das Feld Integer2 verweist? Entweder löst die Laufzeit eine MemberAccessException aus, oder die Methode greift stattdessen auf zufällige Daten auf dem Stapel zu. Beides ist nicht zulässig.
Es ist absolut sicher, eine Strukturvererbung zu haben, solange Sie Strukturen nicht polymorph verwenden oder wenn Sie beim Erben keine Felder hinzufügen. Aber diese sind nicht besonders nützlich.
quelle
Dies scheint eine sehr häufige Frage zu sein. Ich möchte hinzufügen, dass Werttypen "an Ort und Stelle" gespeichert werden, an dem Sie die Variable deklarieren. Abgesehen von Implementierungsdetails bedeutet dies, dass es keinen Objektheader gibt, der etwas über das Objekt aussagt. Nur die Variable weiß, welche Art von Daten sich dort befinden.
quelle
Strukturen unterstützen Schnittstellen, sodass Sie auf diese Weise einige polymorphe Dinge tun können.
quelle
IL ist eine stapelbasierte Sprache. Das Aufrufen einer Methode mit einem Argument sieht also ungefähr so aus:
Wenn die Methode ausgeführt wird, werden einige Bytes vom Stapel entfernt, um das Argument abzurufen. Es weiß genau, wie viele Bytes entfernt werden müssen, da das Argument entweder ein Referenztypzeiger ist (immer 4 Bytes bei 32-Bit) oder ein Werttyp, für den die Größe immer genau bekannt ist.
Wenn es sich um einen Referenztypzeiger handelt, sucht die Methode das Objekt im Heap und erhält das Typhandle, das auf eine Methodentabelle verweist, die diese bestimmte Methode für diesen genauen Typ behandelt. Wenn es sich um einen Wertetyp handelt, ist keine Suche in einer Methodentabelle erforderlich, da Werttypen die Vererbung nicht unterstützen. Daher gibt es nur eine mögliche Kombination aus Methode und Typ.
Wenn Werttypen die Vererbung unterstützen würden, würde dies einen zusätzlichen Aufwand bedeuten, da der bestimmte Typ der Struktur sowie ihr Wert auf dem Stapel platziert werden müssten, was eine Art Methodentabellensuche für die bestimmte konkrete Instanz des Typs bedeuten würde. Dies würde die Geschwindigkeits- und Effizienzvorteile von Werttypen beseitigen.
quelle