Génériques

Références

L'actualité

Librairie

L'information

Introduction

Les génériques ont été ajoutés à la version 2.0 du langage C# et du Common Language Runtime (CLR). Les génériques introduisent le concept des paramètres de type dans .NET Framework, qui permettent de concevoir des classes et des méthodes qui diffèrent la spécification d'un ou de plusieurs types jusqu'à ce que la classe ou la méthode soit déclarée et instanciée par le code client. Par exemple, à l'aide d'un paramètre de type générique T, vous pouvez écrire une seule classe qui peut être utilisée par un autre code client sans impliquer le coût ou le risque des casts ou des opérations de boxing à l'exécution, comme illustré ici :


Vue d'ensemble des génériques

  • Utilisez des types génériques pour optimiser la réutilisation de code, la sécurité des types et les performances.
  • L'utilisation la plus courante des génériques est de créer des classes de collection.
  • La bibliothèque de classes du .NET Framework contient plusieurs nouvelles classes de collection génériques dans l'espace de noms System.Collections.Generic. Celles-ci doivent être utilisées chaque fois que c'est possible, à la place de classes comme ArrayList dans l'espace de noms System.Collections.
  • Vous pouvez créer vos propres interfaces, classes, méthodes, événements et délégués génériques.
  • Les classes génériques peuvent être contraintes pour permettre l'accès à des méthodes sur des types de données particuliers.
  • Des informations sur les types qui sont utilisés dans un type de données générique peuvent être obtenues à l'exécution à l'aide de la réflexion.
Les méthodes et les classes génériques combinent la réutilisabilité, la cohérence des types et l'efficacité, ce que ne peuvent pas faire leurs équivalents non génériques. Les génériques sont plus fréquemment utilisés dans des collections et des méthodes qui agissent sur eux. La version 2.0 de la bibliothèque de classes .NET Framework fournit un nouvel espace de noms, System.Collections.Generic, qui contient plusieurs nouvelles classes de collection génériques. Pour toutes les applications qui ciblent le .NET Framework version 2.0 et ultérieures, il est recommandé d'utiliser les nouvelles classes de collection génériques plutôt que leurs équivalents non génériques, tels que ArrayList. Pour plus d'informations, consultez Génériques en .NET.

Bien sûr, vous pouvez également créer des types et des méthodes génériques personnalisés pour fournir des solutions et des modèles de conception généralisés qui soient efficaces et de type sécurisé. L'exemple de code suivant montre une classe de liste liée générique simple, à des fins de démonstration. Dans la plupart des cas, vous aurez tout intérêt à utiliser la classe List‹T› fournie par la bibliothèque de classes .NET Framework plutôt que de créer la vôtre. Le paramètre de type T est utilisé dans plusieurs emplacements où un type concret est normalement utilisé pour indiquer le type de l'élément stocké dans la liste. Il est utilisé de la façon suivante :

  • Comme le type d'un paramètre de méthode dans la méthode AddHead.
  • Comme le type de retour de la propriété Data de la classe imbriquée Node.
  • Comme le type de membre privé data de la classe imbriquée.

Notez que T est disponible pour la classe imbriquée Node. Quand GenericList‹T› est instancié avec un type concret, par exemple comme un GenericList‹int›, chaque occurrence de T est remplacée par int.

L'exemple de code suivant montre comment le code client utilise la classe générique GenericList‹T› pour créer une liste d'entiers. En changeant l'argument de type, vous pouvez facilement modifier le code suivant pour créer des listes de chaînes ou tout autre type personnalisé :


Avantages des génériques

Les génériques constituent la solution à une limitation dans les versions antérieures du Common Language Runtime et du langage C# dans lesquelles la généralisation s'effectue par un cast de types vers et depuis le type de base universel Object. En créant une classe générique, vous pouvez créer une collection qui est de type sécurisé au moment de la compilation.

Les restrictions relatives à l'utilisation de classes de collections non génériques peuvent être illustrées par l'écriture d'un programme court qui utilise la classe de collection ArrayList de la bibliothèque de classes .NET. Une instance de la classe ArrayList peut stocker n'importe quel type référence ou valeur.

Mais cette commodité a un coût. Tout type référence ou valeur qui est ajouté à un ArrayList est implicitement upcasté en Object. Si les éléments sont des types valeur, ils doivent faire l'objet d'un boxing quand ils sont ajoutés à la liste, et d'un unboxing quand ils sont récupérés. Les opérations de casting aussi bien que les opérations de boxing / unboxing affectent les performances. L'effet du boxing et de l'unboxing peut être très significatif quand vous devez itérer au sein de collections volumineuses.

L'autre limitation réside dans le manque de vérification au moment de la compilation. Étant donné que ArrayList effectue un cast de tous les éléments en Object, il n'existe aucun moyen d'empêcher le code client de procéder comme suit au moment de la compilation :

Bien que parfaitement acceptable et parfois intentionnelle si vous créez une collection hétérogène, la combinaison de chaînes et de int dans un seul ArrayList est plus vraisemblablement une erreur de programmation, et cette erreur ne sera pas détectée avant l'exécution.

Dans les versions 1.0 et 1.1 du langage C#, le seul moyen d'éviter les dangers du code généralisé dans les classes de collections de bibliothèques de classes de base .NET Framework était d'écrire vos propres collections spécifiques à un type. Bien sûr, puisqu'une classe de ce genre n'est pas réutilisable pour plusieurs types de données, vous perdez les avantages de la généralisation, et vous devez réécrire la classe pour chaque type qui sera stocké.

Ce dont ArrayList et d'autres classes semblables ont vraiment besoin est un moyen permettant au code client de spécifier, pour chaque instance, le type de données particulier qu'elles ont l'intention d'utiliser. Cela élimine la nécessité d'effectuer un upcast en Object et permettrait également au compilateur d'effectuer un contrôle de type. En d'autres termes, ArrayList a besoin d'un paramètre de type. C'est exactement ce que fournissent les génériques. Dans la collection List‹T› générique, dans l'espace de noms System.Collections.Generic, la même opération d'ajout d'éléments à la collection ressemble à ceci :

Pour le code client, la seule syntaxe ajoutée avec List‹T› par rapport à ArrayList est l'argument de type dans la déclaration et l'instanciation. En échange de cette complexité de codage légèrement supérieure, vous pouvez créer une liste qui est non seulement plus sûre que ArrayList, mais également considérablement plus rapide, surtout quand les éléments de la liste sont des types valeur.

Paramètres de type générique

Dans une définition de méthode ou un type générique, les paramètres de type représentent un espace réservé pour un type spécifique qu'un client indique quand il instancie une variable du type générique. Une classe générique, telle que GenericList‹T› répertoriée dans Introduction aux génériques, ne peut pas être utilisée en l'état car il ne s'agit pas vraiment d'un type, mais plutôt d'un modèle pour un type. Pour utiliser GenericList‹T›, le code client doit déclarer et instancier un type construit en spécifiant un argument de type à l'intérieur de crochets pointus. L'argument de type pour cette classe particulière peut être tout type reconnu par le compilateur. Il est possible de créer un nombre quelconque d'instances de type construit, chacune avec un argument de type différent, comme suit :

Dans chacune de ces instances de GenericList‹T›, chaque occurrence de T dans la classe sera substituée au moment de l'exécution avec l'argument de type. Par cette substitution, nous avons créé trois objets distincts de type sécurisé et efficaces à l'aide d'une seule définition de classe.

Indications concernant l'attribution d'un nom aux paramètres de type

Nommez les paramètres de type générique avec des noms descriptifs, sauf si un nom composé d'une seule lettre est suffisamment évocateur et qu'un nom descriptif n'apporte rien de plus.

Envisagez l'utilisation de T comme nom de paramètre de type pour les types ayant un paramètre de type d'une seule lettre.

Préfixez les noms des paramètres de type descriptifs avec T.

Pensez à indiquer les contraintes placées sur un paramètre de type dans le nom de paramètre. Par exemple, un paramètre limité à ISession peut être appelé TSession.

Contraintes sur les paramètres de type

Les contraintes informent le compilateur sur les fonctionnalités que doit avoir un argument de type. Sans contrainte, l'argument de type peut être n'importe quel type. Le compilateur peut seulement deviner les membres de Object, qui est la classe de base par excellence de tous les types .NET. Si le code client essaie d'instancier votre classe à l'aide d'un type qui n'est pas autorisé par une contrainte, il en résulte une erreur de compilation. Les contraintes sont spécifiées à l'aide du mot clé contextuel where. Le tableau suivant liste les sept types de contrainte :

Contrainte Description
where T : struct L'argument de type doit être un type valeur. Tout type valeur, excepté Nullable‹T›.
where T : class L'argument de type doit être un type référence. Cette contrainte s'applique également à tous les types de classe, d'interface, de délégué ou de tableau.
where T : unmanaged L'argument de type ne doit pas être un type référence et ne doit contenir aucun membre de type référence à tous les niveaux d'imbrication.
where T : new() L'argument de type doit avoir un constructeur sans paramètre public. Quand vous utilisez la contrainte new() avec d'autres contraintes, elle doit être spécifiée en dernier.
where T : ‹nom_classe_de_base› L'argument de type doit être la classe de base spécifiée ou en dériver.
where T : ‹nom_interface› L'argument de type doit être ou implémenter l'interface spécifiée. Plusieurs contraintes d'interface peuvent être spécifiées. L'interface qui impose les contraintes peut également être générique.
where T : U L'argument de type fourni pour T doit être l'argument fourni pour U ou en dériver.

Certaines contraintes s'excluent mutuellement. Tous les types valeur doivent avoir un constructeur sans paramètre accessible. La contrainte struct implique la contrainte new(), mais la contrainte new() ne peut pas être combinée avec la contrainte struct. La contrainte unmanaged implique la contrainte struct. La contrainte unmanaged ne peut pas être combinée avec les contraintes struct ou new().

Pourquoi utiliser des contraintes

En limitant le paramètre de type, vous augmentez le nombre d'opérations et d'appels de méthode autorisés au niveau de celui pris en charge par le type de contrainte et tous les types dans sa hiérarchie d'héritage. Quand vous concevez des classes ou des méthodes génériques, si vous exécutez des opérations sur les membres génériques au-delà de la simple assignation ou que vous appelez des méthodes non prises en charge par System.Object, vous devez appliquer des contraintes au paramètre de type. Par exemple, la contrainte de classe de base indique au compilateur que seuls les objets de ce type ou dérivés de ce type seront utilisés comme arguments de type. Une fois que le compilateur a cette garantie, il peut autoriser les méthodes de ce type à être appelées dans la classe générique. L'exemple de code suivant illustre la fonctionnalité que vous pouvez ajouter à la classe GenericList (dans Introduction aux génériques) en appliquant une contrainte de classe de base.

---------

La contrainte permet à la classe générique d'utiliser la propriété Employee.Name. La contrainte spécifie que tous les éléments de type T sont soit un objet Employee, soit un objet qui hérite de Employee, et rien d'autre.

Plusieurs contraintes peuvent être appliquées au même paramètre de type, et les contraintes elles-mêmes peuvent être des types génériques, comme suit :

--------

En appliquant la contrainte where T : class, évitez d'utiliser les opérateurs == et != sur le paramètre de type, car ces opérateurs testent uniquement l'identité des références, t non l'égalité des valeurs. Ce comportement se produit même si ces opérateurs sont surchargés dans un type qui est utilisé comme argument. Le code suivant illustre ce point ; la sortie a la valeur false même si la classe String surcharge l'opérateur ==.

-----------

Au moment de la compilation, le compilateur sait uniquement que T est un type référence et doit utiliser les opérateurs par défaut qui sont valides pour tous les types référence. Si vous devez tester l'égalité des valeurs, il est recommandé d'appliquer également la contrainte where T : IEquatable ou where T : IComparable et d'implémenter l'interface dans toute classe qui sera utilisée pour construire la classe générique.

Utilisation de contraintes dans plusieurs paramètres

Vous pouvez appliquer des contraintes à plusieurs paramètres et plusieurs contraintes à un seul paramètre, comme indiqué dans l'exemple suivant :

----------


Paramètres de type unbounded

Les paramètres de type qui n'ont aucune contrainte, tels que T dans la classe publique SampleClass{}, sont appelés paramètres de type unbounded. Les paramètres de type unbounded obéissent aux règles suivantes :

  • Les opérateurs != et == ne peuvent pas être utilisés, car il n'est pas garanti que l'argument de type concret les prendra en charge.
  • Ils peuvent être convertis vers et depuis System.Object ou être explicitement convertis vers tout type d'interface.
  • Vous pouvez les comparer à null. Si un paramètre unbounded est comparé à null, la comparaison retourne toujours la valeur false si l'argument de type est un type valeur.


Paramètres de type en tant que contraintes

L'utilisation d'un paramètre de type générique comme contrainte est utile quand une fonction membre dotée de son propre paramètre de type doit contraindre ce paramètre au paramètre du type conteneur, comme le montre l'exemple suivant :

---------

Dans l'exemple précédent, T est une contrainte de type dans le contexte de la méthode Add et un paramètre de type unbounded dans le contexte de la classe List.

Les paramètres de type peuvent également être utilisés comme contraintes dans les définitions de classes génériques. Le paramètre de type doit être déclaré entre crochets pointus, ainsi que tous les autres paramètres de type :

----------

L'utilité des paramètres de type en tant que contraintes avec les classes génériques est limitée, car le compilateur ne peut rien deviner à propos du paramètre de type en dehors du fait qu'il dérive de System.Object. Utilisez des paramètres de type en tant que contraintes sur les classes génériques dans les scénarios dans lesquels vous souhaitez mettre en application une relation d'héritage entre deux paramètres de type.

Contrainte non managée

à partir de C# 7.3, vous pouvez utiliser la contrainte unmanaged pour spécifier que le paramètre de type doit être un type non managé. Un type non managé est un type qui n'est pas un type référence et ne contient pas de champs de type référence à tous les niveaux d'imbrication. La contrainte unmanaged vous permet d'écrire des routines réutilisables à appliquer aux types qui peuvent être manipulés comme blocs de mémoire, comme illustré dans l'exemple suivant :

------------

La méthode précédente doit être compilée dans un contexte unsafe, car elle utilise l'opérateur sizeof sur un type qui n'est pas connu pour être un type intégré. Sans la contrainte unmanaged, l'opérateur sizeof n'est pas disponible.

Contraintes de délégué

à partir de C# 7.3, vous pouvez aussi utiliser System.Delegate ou System.MulticastDelegate comme contrainte de classe de base. Le CLR a toujours autorisé cette contrainte, contrairement au langage C#. La contrainte System.Delegate vous permet d'écrire du code qui fonctionne avec les délégués en mode type sécurisé. Le code suivant définit une méthode d'extension qui combine deux délégués du moment qu'ils sont du même type :

----------

Vous pouvez utiliser la méthode ci-dessus pour combiner des délégués qui sont du même type :

---------

Si vous supprimez les commentaires de la dernière ligne, il ne sera pas compilé. first et test sont tous deux des types délégués, mais des types délégués différents.

Contraintes d'enum

à compter de C# 7.3, vous pouvez également spécifier le type System.Enum comme contrainte de classe de base. Le CLR a toujours autorisé cette contrainte, contrairement au langage C#. Les génériques utilisant System.Enum fournissent une programmation de type sécurisé aux résultats de cache issus de l'utilisation de méthodes statiques dans System.Enum. L'exemple suivant recherche toutes les valeurs valides d'un type enum, puis génère un dictionnaire qui mappe ces valeurs à sa représentation sous forme de chaîne.

----------

Les méthodes utilisées font appel à la réflexion, ce qui a des implications en termes de performances. Vous pouvez appeler cette méthode pour générer une collection qui est mise en cache et réutilisée, plutôt que de répéter les appels qui nécessitent la réflexion.

Vous pouvez l'utiliser comme montré dans l'exemple suivant pour créer un enum et générer un dictionnaire de ses valeurs et de ses noms :

-----------


----------


Classes génériques

Les classes génériques encapsulent des opérations qui ne sont pas spécifiques à un type de données particulier. Les classes génériques sont le plus souvent utilisées avec les collections telles que les listes liées, les tables de hachage, les piles, les files d'attente, les arborescences, etc. Les opérations telles que l'ajout et la suppression d'éléments dans la collection sont exécutées fondamentalement de la même manière quel que soit le type des données stockées.

Pour la plupart des scénarios qui nécessitent des classes de collections, l'approche recommandée consiste à utiliser celles fournies dans la bibliothèque de classes .NET. Pour plus d'informations sur l'utilisation de ces classes, consultez Collections génériques dans .NET.

En général, vous créez des classes génériques en commençant par une classe concrète existante et en changeant des types en paramètres de type individuellement jusqu'à ce que vous atteigniez l'équilibre optimal au niveau de la généralisation et de la facilité d'utilisation. Lors de la création de vos propres classes génériques, les aspects importants à prendre en considération sont notamment les suivants :

Quels types généraliser en paramètres de type. En règle générale, plus le nombre de types que vous pouvez paramétrer est important, plus votre code est souple et réutilisable. Toutefois, une généralisation excessive peut produire un code qui est difficile à lire ou à comprendre par d'autres développeurs.

Quelles contraintes, le cas échéant, appliquer aux paramètres de type (consultez Contraintes sur les paramètres de type). Il est pertinent d'appliquer le maximum de contraintes possible vous permettant néanmoins de gérer les types que vous devez gérer. Par exemple, si vous savez que votre classe générique est destinée uniquement à être utilisée avec les types référence, appliquez la contrainte de classe. Cela empêchera l'utilisation involontaire de votre classe avec les types valeur et vous permettra d'utiliser l'opérateur as sur T et de rechercher les valeurs nulles.

S'il convient de prendre en compte le comportement générique dans les sous-classes et les classes de base. Les classes génériques pouvant servir de classes de base, les considérations de conception qui s'appliquent ici sont les mêmes que pour les classes non génériques. Consultez les règles relatives à l'héritage à partir des classes de base génériques plus loin dans cette rubrique.

S'il convient d'implémenter une ou plusieurs interfaces génériques. Par exemple, si vous concevez une classe qui sera utilisée pour créer des éléments dans une collection basée sur les génériques, vous pouvez avoir besoin d'implémenter une interface comme IComparable, où T est le type de votre classe.




Pour obtenir un exemple d'une classe générique simple, consultez Introduction aux génériques.

Les règles pour les paramètres de type et les contraintes ont plusieurs conséquences sur le comportement de classe générique, surtout en ce qui concerne l'héritage et l'accessibilité des membres. Avant de continuer, vous devez comprendre quelques termes. Pour une classe générique Node,, le code client peut référencer la classe soit en spécifiant un argument de type, pour créer un type construit fermé (Node), soit en laissant le paramètre de type non spécifié, par exemple quand vous spécifiez une classe de base générique, pour créer un type construit ouvert (Node). Les classes génériques peuvent hériter de classes de base concrètes, construites fermées ou construites ouvertes :

----------

Les classes non génériques, également appelées concrètes, peuvent hériter des classes de base construites fermées, mais pas des classes construites ouvertes ni des paramètres de type parce que le code client ne peut en aucune façon fournir au moment de l'exécution l'argument de type requis pour instancier la classe de base.

-----------

Les classes génériques qui héritent de types construits ouverts doivent fournir des arguments de type pour tout paramètre de type de classe de base qui n'est pas partagé par la classe qui hérite, comme cela est indiqué dans le code suivant :

-----------

Les classes génériques qui héritent des types construits ouverts doivent spécifier des contraintes qui sont un sur-ensemble, ou qui impliquent, des contraintes sur le type de base :

----------

Les types génériques peuvent utiliser plusieurs paramètres de type et contraintes, comme suit :

---------

Les types construits ouverts et les types construits fermés peuvent être utilisés comme paramètres de méthode :

---------

Si une classe générique implémente une interface, toutes les instances de cette classe peuvent être castées à cette interface.

Les classes génériques sont indifférentes. En d'autres termes, si un paramètre d'entrée spécifie un List, vous obtiendrez une erreur de compilation si vous essayez de fournir un List.

Interfaces génériques

Il est souvent utile de définir des interfaces pour les classes de collections génériques, ou pour les classes génériques qui représentent des éléments dans la collection. Pour les classes génériques, il est préférable d'utiliser des interfaces génériques, comme IComparable à la place d'IComparable, pour éviter les opérations de boxing et d'unboxing sur les types valeur. La bibliothèque de classes .NET Framework définit plusieurs interfaces génériques à utiliser avec les classes de collections dans l'espace de noms System.Collections.Generic.

Quand une interface est spécifiée comme une contrainte sur un paramètre de type, seuls les types qui implémentent l'interface peuvent être utilisés. L'exemple de code suivant illustre une classe SortedList qui dérive de la classe GenericList. Pour plus d'informations, consultez Introduction aux génériques. SortedList ajoute la contrainte where T : IComparable. Ceci permet à la méthode BubbleSort dans SortedList d'utiliser la méthode générique CompareTo sur les éléments de liste. Dans cet exemple, les éléments de liste sont une classe simple, Person, qui implémente IComparable.

--------

Plusieurs interfaces peuvent être spécifiées en tant que contraintes sur un seul type, comme suit :

--------

Une interface peut définir plusieurs paramètres de type, comme suit :

--------

Les règles d'héritage qui s'appliquent aux classes s'appliquent également aux interfaces :

----------

Les interfaces génériques peuvent hériter d'interfaces non génériques si l'interface générique est de type contravariant, ce qui signifie qu'elle utilise uniquement son paramètre de type comme valeur de retour. Dans la bibliothèque de classes .NET Framework, IEnumerable hérite d'IEnumerable, car IEnumerable utilise uniquement T dans la valeur de retour de GetEnumerator et dans l'accesseur Get de propriété Current.

Les classes concrètes peuvent implémenter des interfaces construites fermées, comme suit :

---------

Les classes génériques peuvent implémenter des interfaces génériques ou des interfaces construites fermées tant que la liste de paramètres de classe fournit tous les arguments requis par l'interface, comme suit :

--------

Les règles qui déterminent la surcharge des méthodes sont les mêmes pour les méthodes dans les classes génériques, les structs génériques ou les interfaces génériques. Pour plus d'informations, consultez Méthodes génériques.

Méthodes génériques

Une méthode générique est une méthode qui est déclarée avec des paramètres de type, comme suit :

--------

L'exemple de code suivant montre une manière d'appeler la méthode, avec int comme argument de type :

--------

Vous pouvez également omettre l'argument de type et le compilateur le déduira. L'appel suivant à Swap est équivalent à l'appel précédent :

--------

Les mêmes règles d'inférence de type s'appliquent aux méthodes statiques et aux méthodes d'instance. Le compilateur peut déduire les paramètres de type d'après les arguments de méthode que vous passez. Il ne peut pas déduire les paramètres de type uniquement à partir d'une valeur de contrainte ou de retour. Par conséquent, l'inférence de type ne fonctionne pas avec les méthodes qui n'ont pas de paramètres. L'inférence de type se produit au moment de la compilation avant que le compilateur n'essaie de résoudre toute signature de méthode surchargée. Le compilateur applique la logique d'inférence de type à toutes les méthodes génériques qui partagent le même nom. à l'étape de résolution de la surcharge, le compilateur inclut uniquement les méthodes génériques sur lesquelles l'inférence de type a réussi.

Dans une classe générique, les méthodes non génériques peuvent accéder aux paramètres de type de niveau classe, comme suit :

---------

Si vous définissez une méthode générique qui accepte les mêmes paramètres de type que la classe conteneur, le compilateur génère l'avertissement CS0693, car l'argument fourni à l'intérieur de la portée de la méthode pour le T interne masque l'argument fourni pour le T externe. Si vous avez éventuellement besoin d'appeler une méthode de classe générique avec des arguments de type différents de ceux qui ont été fournis quand la classe a été instanciée, envisagez de fournir un autre identificateur pour le paramètre de type de la méthode, comme indiqué dans GenericList2 dans l'exemple suivant.

----------

Utilisez des contraintes pour permettre des opérations plus spécialisées sur les paramètres de type dans les méthodes. Cette version de Swap, maintenant appelée SwapIfGreater, peut être utilisée avec des arguments de type qui implémentent IComparable uniquement.

---------

Les méthodes génériques peuvent être surchargées sur plusieurs paramètres de type. Par exemple, les méthodes suivantes peuvent toutes être dans la même classe :

---------


Génériques et tableaux

En C# 2.0 et versions ultérieures, les tableaux unidimensionnels qui ont une limite inférieure de zéro implémentent automatiquement IList. Cela vous permet de créer des méthodes génériques qui peuvent utiliser le même code pour itérer au sein de tableaux et d'autres types de collections. Cette technique est essentiellement utile pour lire les données dans des collections. L'interface IList ne peut pas être utilisée pour ajouter ni supprimer des éléments dans un tableau. Une exception est levée si vous essayez d'appeler une méthode IList telle que RemoveAt sur un tableau dans ce contexte.

L'exemple de code suivant montre comment une méthode générique seule qui prend un paramètre d'entrée IList peut itérer à la fois au sein d'une liste et d'un tableau (en l'occurrence un tableau d'entiers).

---------


Génériques et délégués

Un délégué peut définir ses propres paramètres de type. Le code qui référence le délégué générique peut spécifier l'argument de type pour créer un type construit fermé, comme lors de l'instanciation d'une classe générique ou d'un appel d'une méthode générique, ainsi que l'illustre l'exemple ci-dessous :

--------

Le langage C# version 2.0 propose une nouvelle fonctionnalité appelée conversion de groupe de méthodes, qui s'applique aussi bien aux types concrets qu'aux types délégués génériques et vous permet d'écrire la ligne précédente avec la syntaxe simplifiée suivante :

--------

Les délégués définis dans une classe générique peuvent utiliser les paramètres de type de classe générique, à l'instar des méthodes de classe.

----------

Le code qui référence le délégué doit spécifier l'argument de type de la classe conteneur, comme suit :

----------

Les délégués génériques sont particulièrement utiles pour définir des événements basés sur le modèle de design type parce que l'argument de l'expéditeur peut être fortement typé et il n'est plus nécessaire d'en effectuer un cast vers et depuis Object.

----------


Génériques dans le runtime

Quand une méthode ou un type générique est compilé dans le langage intermédiaire Microsoft (MSIL), il ou elle contient des métadonnées qui l'identifient comme ayant des paramètres de type. La façon dont le langage MSIL d'un type générique est utilisé diffère selon que le paramètre de type fourni est un type valeur ou 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 paramètre fourni ou les paramètres substitués aux emplacements appropriés dans le langage MSIL. 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 :

---------

à ce stade, le runtime génère une version spécialisée de la classe Stack 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 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 :

---------

Toutefois, supposons qu'une autre classe Stack 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. Le runtime génère alors une autre version du type générique et substitue un type long aux emplacements appropriés dans le langage MSIL. 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 MSIL. 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 :

-------------


-----------

à ce stade, le runtime génère une version spécialisée de la classe Stack 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 :

--------

Contrairement aux types valeur, une autre version spécialisée de la classe Stack n'est pas créée pour le type Order. à la place, une instance de la version spécialisée de la classe Stack 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 :

---------

Comme avec l'utilisation précédente de la classe Stack créée à l'aide du type Order, une autre instance de la classe Stack 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 de l'exécution, et son type réel ainsi que son paramètre de type peuvent être vérifiés.


Génériques et réflexion

étant donné que le common language runtime (CLR) a accès aux informations concernant les types génériques au moment de l'exécution, vous pouvez utiliser la réflexion pour obtenir des informations sur les types génériques de la même manière que pour les types non génériques. Pour plus d'informations, consultez Génériques dans le runtime.

Dans le .NET Framework 2.0, plusieurs nouveaux membres ont été ajoutés à la classe Type pour générer des informations d'exécution pour les types génériques. Pour plus d'informations sur l'utilisation de ces méthodes et de ces propriétés, consultez la documentation relative à ces classes. L'espace de noms System.Reflection.Emit contient également de nouveaux membres qui prennent en charge les génériques. Voir Guide pratique pour définir un type générique avec l'émission de réflexion.

Pour obtenir la liste des conditions indifférentes pour les termes utilisés dans la réflexion générique, consultez les notes sur la propriété IsGenericType.


----- tableau

De plus, les membres de la classe MethodInfo permettent la génération d'informations d'exécution pour les méthodes génériques. Pour obtenir la liste des conditions indifférentes des termes utilisés pour réfléchir les méthodes génériques, consultez les notes relatives à la propriété IsGenericMethod.

----- tableau


Génériques et attributs

Les attributs peuvent être appliqués aux types génériques de la même manière qu'aux types non génériques. Pour plus d'informations sur l'application des attributs, consultez Attributs.

Les attributs personnalisés sont uniquement autorisés à référencer des types génériques ouverts, qui sont des types génériques pour lesquels aucun argument de type n'est fourni, et des types génériques construits fermés, qui fournissent des arguments pour tous les paramètres de type.

Les exemples suivants utilisent cet attribut personnalisé :

--------

Un attribut peut référencer un type générique ouvert :

--------

Spécifiez plusieurs paramètres de type à l'aide du nombre approprié de virgules. Dans cet exemple, GenericClass2 a deux paramètres de type :

-------

Un attribut peut référencer un type générique construit fermé :

----------

Un attribut qui référence un paramètre de type générique entraîne une erreur de compilation :

---------

Un type générique ne peut pas hériter d'Attribute :

----------

Pour obtenir des informations sur un type générique ou un paramètre de type au moment de l'exécution, vous pouvez utiliser les méthodes de System.Reflection. Pour plus d'informations, consultez Génériques et réflexion