C'est la question du point de vue interne du compilateur.
Je m'intéresse aux génériques, pas aux modèles (C ++), j'ai donc marqué la question avec C #. Pas Java, car AFAIK les génériques dans les deux langues diffèrent dans les implémentations.
Quand je regarde les langages sans génériques, c'est assez simple, vous pouvez valider la définition de classe, l'ajouter à la hiérarchie et c'est tout.
Mais que faire de la classe générique, et surtout comment gérer les références à celle-ci? Comment s'assurer que les champs statiques sont singuliers par instanciation (c'est-à-dire à chaque fois que les paramètres génériques sont résolus).
Disons que je vois un appel:
var x = new Foo<Bar>();
Dois-je ajouter une nouvelle Foo_Bar
classe à la hiérarchie?
Mise à jour: Jusqu'à présent, je n'ai trouvé que 2 messages pertinents, mais même ils n'entrent pas dans les détails dans le sens "comment le faire par vous-même":
Réponses:
Chaque instanciation générique a sa propre copie de la table de méthodes (nommée de manière confuse), qui est où les champs statiques sont stockés.
Je ne suis pas sûr qu'il soit utile de considérer la hiérarchie des classes comme une structure qui existe réellement au moment de l'exécution, c'est plus une construction logique.
Mais si vous considérez MethodTables, chacune avec un pointeur indirect sur sa classe de base, pour former cette hiérarchie, alors oui, cela ajoute une nouvelle classe à la hiérarchie.
la source
Foo<string>
et ils ne produiront pas deux instances de champ statique à partir deFoo
.Je vois là deux vraies questions concrètes. Vous voudrez probablement poser des questions connexes supplémentaires (en tant que question distincte avec un lien vers celui-ci) pour bien comprendre.
Comment les champs statiques reçoivent-ils des instances distinctes par instance générique?
Eh bien, pour les membres statiques qui ne sont pas liés aux paramètres de type générique, c'est assez facile (utilisez un dictionnaire mappé des paramètres génériques à la valeur).
Les membres (statiques ou non) liés aux paramètres de type peuvent être gérés via l'effacement de type. Utilisez simplement la contrainte la plus forte (souvent
System.Object
). Étant donné que les informations de type sont effacées après les vérifications de type du compilateur, cela signifie que les vérifications de type d'exécution ne seront pas nécessaires (bien que les conversions d'interface puissent toujours exister au moment de l'exécution).Chaque instance générique apparaît-elle séparément dans la hiérarchie de types?
Pas dans les génériques .NET. La décision a été prise d'exclure l'héritage des paramètres de type, il s'avère donc que toutes les instances d'un générique occupent la même place dans la hiérarchie de types.
C'était probablement une bonne décision, car ne pas rechercher des noms dans une classe de base serait incroyablement surprenant.
la source
Foo<int>
etFoo<string>
frapperaient les mêmes donnéesFoo
sans contraintes.List<string>
etList<Form>
, puisqu'unList<T>
membre de type est interneT[]
et qu'il n'y a pas de contraintesT
, alors vous obtiendrez en fait un code machine qui manipule unobject[]
. Cependant, comme seules lesT
instances sont placées dans le tableau, tout ce qui sort peut être retourné sous forme deT
sans vérification de type supplémentaire. D'un autre côté, si vous l'aviezControlCollection<T> where T : Control
, alors le tableau interneT[]
deviendraitControl[]
.La manière générale dans le frontal du compilateur est d'avoir deux sortes d'instances de type, le type générique (
List<T>
) et un type générique lié (List<Foo>
). Le type générique définit quelles fonctions existent, quels champs et a des références de type générique partout où ilT
est utilisé. Le type générique lié contient une référence au type générique et un ensemble d'arguments de type. Cela contient suffisamment d'informations pour que vous puissiez ensuite générer un type concret, en remplaçant les références de type génériques parFoo
ou quels que soient les arguments de type. Ce type de distinction est important lorsque vous faites une inférence de type et que vous devez déduireList<T>
par rapport àList<Foo>
.Au lieu de penser à des génériques comme des modèles (qui construisent directement diverses implémentations), il peut être utile de les considérer plutôt comme des constructeurs de types de langage fonctionnel (où les arguments génériques sont comme des arguments dans une fonction qui vous donne un type).
Quant au back-end, je ne sais pas vraiment. Tout mon travail avec les génériques a ciblé CIL comme backend, donc je pouvais les compiler dans les génériques pris en charge.
la source
List<T>
détient le type réel (sa définition), tandis queList<Foo>
(merci également pour la terminologie) avec mon approche, les déclarations deList<T>
(bien sûr maintenant liées àFoo
au lieu deT
).