Génériques dans le runtime (guide de programmation C#)

Quand une méthode ou un type générique est compilé en langage CIL (Common Intermediate Language), il contient des métadonnées qui l’identifient comme ayant des paramètres de type. La façon dont le langage CIL pour un type générique est utilisé diffère selon que le paramètre de type fourni est un type valeur ou un type référence.

Quand un type générique est construit en premier avec un type valeur comme paramètre, le runtime crée un type générique spécialisé avec le ou les paramètres fournis remplacés aux emplacements appropriés dans le langage CIL. Des types génériques spécialisés sont créés une fois pour chaque type valeur unique qui est utilisé comme paramètre.

Par exemple, supposons que votre code de programme ait déclaré une pile construite d’entiers :

Stack<int>? stack;

À ce stade, le runtime génère une version spécialisée de la classe Stack<T> dont l’entier a été substitué correctement à son paramètre. Maintenant, chaque fois que le code de programme utilise une pile d’entiers, le runtime réutilise la classe Stack<T> spécialisée générée. Dans l’exemple suivant, deux instances d’une pile d’entiers sont créées, et elles partagent une instance unique du code Stack<int> :

Stack<int> stackOne = new Stack<int>();
Stack<int> stackTwo = new Stack<int>();

Toutefois, supposons qu’une autre classe Stack<T> avec un type valeur différent tel qu’un type long ou une structure définie par l’utilisateur comme son paramètre est créée à un autre endroit dans votre code. En conséquence, le runtime génère une autre version du type générique et utilise un long aux endroits appropriés dans le langage CIL. Les conversions ne sont plus nécessaires parce que chaque classe générique spécialisée contient le type valeur en mode natif.

Les génériques fonctionnent un peu différemment pour les types référence. La première fois qu’un type générique est construit avec un type référence quelconque, le runtime crée un type générique spécialisé avec les références d’objet substituées aux paramètres dans le langage CIL. Ensuite, chaque fois qu’un type construit est instancié avec un type référence comme son paramètre, indépendamment de son type, le runtime réutilise la version spécialisée créée précédemment pour le type générique. Ceci est possible parce que toutes les références ont la même taille.

Par exemple, supposons que vous ayez deux types référence, une classe Customer et une classe Order, et supposons également que vous ayez créé une pile de types Customer :

class Customer { }
class Order { }
Stack<Customer> customers;

À ce stade, le runtime génère une version spécialisée de la classe Stack<T> qui stocke des références d’objet qui seront remplies ultérieurement au lieu de stocker des données. Supposons que la ligne de code suivante crée une pile d’un autre type référence, qui est nommé Order :

Stack<Order> orders = new Stack<Order>();

Contrairement aux types valeur, une autre version spécialisée de la classe Stack<T> n’est pas créée pour le type Order. À la place, une instance de la version spécialisée de la classe Stack<T> est créée et la variable orders est définie pour la référencer. Supposons que vous ayez rencontré ensuite une ligne de code pour créer une pile d’un type Customer :

customers = new Stack<Customer>();

Comme avec l’utilisation précédente de la classe Stack<T> créée à l’aide du type Order, une autre instance de la classe Stack<T> spécialisée est créée. Les pointeurs contenus ici sont configurés pour référencer une zone de mémoire de la taille d’un type Customer. Dans la mesure où le nombre de types référence peut varier de manière importante d’un programme à l’autre, l’implémentation C# de génériques réduit sensiblement le volume du code en ramenant à une unité le nombre de classes spécialisées créées par le compilateur pour les classes génériques de types référence.

De plus, quand une classe C# générique est instanciée à l’aide d’un paramètre de type valeur ou référence, la réflexion peut l’interroger au moment du temps d’exécution, et son type réel ainsi que son paramètre de type peuvent être vérifiés.

Voir aussi