Dies ist die Frage aus Sicht der Compiler-Interna.
Ich interessiere mich für Generika, nicht für Vorlagen (C ++), daher habe ich die Frage mit C # markiert. Nicht Java, da AFAIK die Generika in beiden Sprachen in Implementierungen unterscheiden.
Wenn ich mir Sprachen ohne Generika anschaue, ist das ziemlich einfach. Sie können die Klassendefinition validieren, der Hierarchie hinzufügen und fertig.
Aber was tun mit generischer Klasse und was noch wichtiger ist, wie mit Verweisen darauf umzugehen ist? So stellen Sie sicher, dass statische Felder pro Instanziierung singulär sind (dh jedes Mal, wenn generische Parameter aufgelöst werden).
Nehmen wir an, ich sehe einen Anruf:
var x = new Foo<Bar>();
Füge ich Foo_Bar
der Hierarchie eine neue Klasse hinzu?
Update: Bisher habe ich nur 2 relevante Beiträge gefunden, aber auch sie gehen nicht auf viele Details im Sinne von "Wie mache ich das selbst?" Ein:
Antworten:
Jede generische Instanziierung verfügt über eine eigene Kopie der (verwirrend benannten) MethodTable, in der statische Felder gespeichert werden.
Ich bin mir nicht sicher, ob es sinnvoll ist, sich die Klassenhierarchie als eine Struktur vorzustellen, die tatsächlich zur Laufzeit existiert. Es ist eher ein logisches Konstrukt.
Wenn Sie jedoch MethodTables berücksichtigen, von denen jede einen indirekten Zeiger auf ihre Basisklasse enthält, um diese Hierarchie zu bilden, wird der Hierarchie eine neue Klasse hinzugefügt.
quelle
Foo<string>
nicht zwei Instanzen eines statischen Felds erzeugt werdenFoo
.Ich sehe dort zwei konkrete Fragen. Sie möchten wahrscheinlich zusätzliche verwandte Fragen stellen (als separate Frage mit einem Link zurück zu dieser), um ein umfassendes Verständnis zu erhalten.
Wie werden statische Felder mit separaten Instanzen pro generischer Instanz versehen?
Nun, für statische Elemente, die sich nicht auf die generischen Typparameter beziehen, ist dies ziemlich einfach (verwenden Sie ein Wörterbuch, das von den generischen Parametern auf den Wert abgebildet wird).
Elemente (statisch oder nicht), die sich auf die Typparameter beziehen, können über das Löschen von Typen behandelt werden. Verwenden Sie einfach (oft
System.Object
) die stärkste Einschränkung . Da die Typinformationen nach Compiler-Typprüfungen gelöscht werden, sind Laufzeit-Typprüfungen nicht erforderlich (obwohl zur Laufzeit möglicherweise noch Schnittstellenumwandlungen vorhanden sind).Erscheint jede generische Instanz separat in der Typhierarchie?
Nicht in .NET-Generika. Es wurde entschieden, die Vererbung von den Typparametern auszuschließen, sodass sich herausstellt, dass alle Instanzen eines Generikums an derselben Stelle in der Typhierarchie befinden.
Dies war wahrscheinlich eine gute Entscheidung, da es unglaublich überraschend wäre, keine Namen aus einer Basisklasse nachzuschlagen.
quelle
Foo<int>
undFoo<string>
dieselben Daten ohneFoo
Einschränkungen.List<string>
und habenList<Form>
, daList<T>
intern ein Element vom Typ vorhanden istT[]
und es keine Einschränkungen gibtT
, erhalten Sie tatsächlich Maschinencode, der ein Element manipuliertobject[]
. Da jedoch nurT
Instanzen in das Array eingefügt werden, kann alles, was herauskommt,T
ohne zusätzliche Typprüfung als zurückgegeben werden. Wenn Sie diesControlCollection<T> where T : Control
getanT[]
hätten, wäre das interne Array gewordenControl[]
.Der allgemeine Weg im Frontend des Compilers besteht darin, zwei Arten von Typinstanzen zu haben, den generischen Typ (
List<T>
) und einen gebundenen generischen Typ (List<Foo>
). Der generische Typ definiert, welche Funktionen vorhanden sind, welche Felder und hat generische Typreferenzen, woT
immer verwendet wird. Der gebundene generische Typ enthält einen Verweis auf den generischen Typ und eine Reihe von Typargumenten. Damit haben Sie genügend Informationen, um dann einen konkreten Typ zu generieren und die generischen Typreferenzen durchFoo
oder unabhängig von den Typargumenten zu ersetzen . Diese Art der Unterscheidung ist wichtig, wenn Sie Typinferenz machen undList<T>
versus ableiten müssenList<Foo>
.Anstatt an Generika wie Vorlagen zu denken (die verschiedene Implementierungen direkt aufbauen), kann es hilfreich sein, sie als Konstruktoren für funktionale Sprachtypen zu betrachten (wobei die generischen Argumente wie Argumente in einer Funktion sind, die Ihnen einen Typ gibt).
Was das Backend angeht, weiß ich es nicht wirklich. Alle meine Arbeiten mit Generika zielten auf CIL als Backend ab, sodass ich sie dort zu den unterstützten Generika kompilieren konnte.
quelle
List<T>
gilt der echte Typ (seine Definition), währendList<Foo>
(danke auch für das Terminologiestück) mit meinem Ansatz die Erklärungen vonList<T>
(natürlich jetzt gebunden an) enthaltenFoo
stattT
).