Maison  >  Article  >  développement back-end  >  Présentation et utilisations spécifiques des génériques

Présentation et utilisations spécifiques des génériques

零下一度
零下一度original
2017-06-23 16:32:421495parcourir

1. Présentation des génériques

Les classes génériques et les méthodes génériques ont à la fois une réutilisabilité, une sécurité de type et une haute efficacitéIl est tout en un, ce qui est hors de portée des classes et méthodes non génériques correspondantes. Les génériques sont largement utilisés dans les conteneurs (collections) et les méthodes qui fonctionnent sur les conteneurs. La bibliothèque de classes .NET Framework 2.0 fournit un nouvel espace de noms System.Collections.Generic, qui contient de nouvelles classes de conteneurs génériques.

  1. Paramètres de type variable génériques : généralement T, mais tous les mots non-clés et mots réservés peuvent également être utilisés ;

  2. Tous les types de variables T sont sous la forme d'espaces réservés au moment de la compilation, et tous les points seront remplacés par les types réellement transmis au moment de l'exécution

2. Avantages des génériques

Pour les premières versions du Common Language Runtime et les limitations du langage C#, les génériques offrent une solution. La généralisation des types précédents a été réalisée en convertissant le type en classe de base globale System.Object.

La classe conteneur ArrayList de la bibliothèque de classes de base du .NET Framework est un exemple de cette limitation. ArrayList est une classe conteneur très pratique qui peut stocker n'importe quel type de référence ou type de valeur sans le modifier pendant l'utilisation.

ArrayList list = new ArrayList();
list.Add(1);
list.Add(175.50);
list.Add("hello kitty");

ArrayList list = new ArrayList();
list.Add(1);
list.Add(175.50);
list.Add("hello kitty");

double sum = 0;
foreach(int value in list)
{
     sum += value;
}

double somme = 0;

foreach(valeur int dans la liste)
{
somme += valeur ;
}

Inconvénients :

La commodité a un prix, qui nécessite que tout type de référence ou type de valeur ajouté à ArrayList soit implicitement converti en System.Object. Si les éléments sont de type valeur, ils doivent être encadrés lorsqu’ils sont ajoutés à la liste et déballés lorsqu’ils sont récupérés. Les conversions de types et les opérations de boxing et de unboxing réduisent toutes les performances ; l’impact du boxing et du unboxing peut être important lorsque de grands conteneurs doivent être itérés. Une autre limitation est le manque de vérification de type au moment de la compilation. Lorsqu'un ArrayList convertit n'importe quel type en Object, il ne peut pas empêcher les erreurs telles que sum+=vlaue dans le code client au moment de la compilation

    List listInt = new List();
     listInt.Add(100);
     listInt.Add(200);
     listInt.Add(123.112); //编译报错
     listInt.Add("heel");  //编译报错

    double sum = 0;
     foreach (int value in list)
     {
         sum += value;
     }

dans System In générique ; List dans l'espace de noms .Collections.Generic, la même opération est effectuée pour ajouter des éléments au conteneur, similaire à ceci :

List listInt = new List();
listInt.Add(100);
listInt.Add(200);
listInt.Add(123.112); //Erreur de compilation
listInt.Add("heel"); //Erreur de compilation

double somme = 0;
foreach (valeur int dans la liste)
                                             somme += valeur ;                                                                    En comparaison , le seul ajout à la syntaxe List dans le code client concerne les paramètres de type dans la déclaration et l'instanciation. La récompense d'un code légèrement plus complexe est que la table que vous créez est non seulement plus sûre qu'une ArrayList, mais également beaucoup plus rapide, en particulier lorsque les éléments de la table sont des types valeur.

3. Paramètres de type générique

List listInt = new List();
List listFloat = new List();
List listString = new List();

Dans la définition d'un type ou d'une méthode générique, le paramètre de type est un espace réservé, généralement des lettres majuscules (vous pouvez également utiliser n'importe quelle noms de mots non-clés et de mots réservés), tels que T. Lorsque le code client déclare et instancie une variable de ce type, remplacez T par le type de données spécifié par le code client. Une classe générique, telle que la classe List donnée dans Generics, ne peut pas être utilisée telle quelle car il ne s'agit pas d'un vrai type mais plutôt d'un modèle de type. Pour utiliser MyList, le code client doit déclarer et instancier un type construit en spécifiant un paramètre de type entre crochets. Les paramètres de type de cette classe particulière peuvent être de n'importe quel type reconnu par le compilateur. N'importe quel nombre d'instances d'un type construit peut être créé, chacune utilisant des paramètres de type différents, comme suit :
Liste listInt = new List();
List listFloat = new List();
List ) ;

4. Contraintes sur les paramètres de type générique

Les génériques fournissent les cinq contraintes suivantes :

约束 描述
where T : struct 参数类型必须为值类型
where T : class 参数类型必须为引用类型
where T : new() 参数类型必须有一个公有的无参构造函数。当与其它约束联合使用时,new()约束必须放在最后。
where T : 参数类型必须为指定的基类型或派生自指定基类型的子类
where T : 参数类型必须为指定的接口或指定接口的实现。可指定多个接口的约束。接口约束也可以是泛型的。

Paramètres de type sans restriction :

  1. Impossible d'utiliser != et == pour comparer des instances de types mutables car il n'y a aucune garantie de spécificité. Les paramètres de type prennent en charge ces opérateurs

  2. Ils peuvent être convertis vers et depuis System.Object, ou explicitement convertis vers n'importe quel type d'interface

  3. peut être comparé à null. Si un paramètre de type non qualifié est comparé à null, le résultat de la comparaison est toujours faux lorsque le paramètre de type est un type valeur.

Aucune contrainte de type : Lorsque la contrainte est un paramètre de type générique, on l'appelle contraintes de type Naked.

class List
{
     void Add(List items) where U : T
     {
     }
}

class List
{
      void Add(List< U> éléments) où U : T

Dans l'exemple ci-dessus, T dans le contexte de la méthode Add est une contrainte non typée ; tandis que T dans le contexte de la classe List est un paramètre de type illimité.

Les contraintes non typées peuvent également être utilisées dans la définition de classes génériques. Notez que les contraintes non typées doivent également être déclarées entre crochets avec d'autres paramètres de type :

//contrainte de type nue

classe publique MyClass< T,U,V> où T : V

Parce que le compilateur pense uniquement que les contraintes non typées sont héritées de System.Object, les classes génériques avec des contraintes non typées ont des utilisations très limitées. Utilisez une contrainte non typée sur une classe générique lorsque vous souhaitez appliquer une relation d'héritage entre deux paramètres de type.

5. Classes génériques

Les classes génériques encapsulent des opérations qui ne sont spécifiques à aucun type de données spécifique. Les classes génériques sont souvent utilisées dans les classes conteneurs, telles que les listes chaînées, les tables de hachage, les piles, les files d'attente, les arbres, etc. Les opérations dans ces classes, telles que l'ajout et la suppression d'éléments du conteneur, effectuent presque les mêmes opérations quel que soit le type de données stockées.

En général, créez une classe générique à partir d'une classe concrète existante et modifiez le type pour saisir les paramètres un par un jusqu'à ce que le meilleur équilibre entre généralité et convivialité soit atteint. Lors de la création de vos propres classes génériques, les éléments importants à prendre en compte sont :

  • Quels types doivent être généralisés aux paramètres de type. La règle générale est que plus il y a de types représentés par des paramètres, plus le code est flexible et réutilisable. Trop de généralisations peut rendre le code difficile à comprendre par les autres développeurs.

  • S'il y a des contraintes, quelles contraintes sont requises pour les paramètres de type. Une bonne pratique consiste à utiliser les contraintes les plus grandes possibles tout en garantissant que tous les types qui doivent être traités peuvent l'être. Par exemple, si vous savez que votre classe générique va uniquement utiliser des types référence, appliquez les contraintes pour cette classe. Cela empêche l'utilisation par inadvertance de types valeur, et vous pouvez utiliser l'opérateur as sur T et vérifier les références nulles

  • Mettre le comportement générique dans la classe de base Ou dans ; une sous-catégorie. Les classes génériques peuvent être utilisées comme classes de base. Ceci doit également être pris en compte lors de la conception de classes non génériques. Règles d'héritage pour les classes de base génériques ;

  • S'il faut implémenter une ou plusieurs interfaces génériques. Par exemple, pour concevoir une classe qui crée des éléments dans un conteneur basé sur des génériques, vous souhaiterez peut-être implémenter une interface telle que IComparable, où T est un paramètre de la classe.

Pour une classe générique Node, le code client peut soit spécifier un paramètre de type pour créer un type construit fermé (Node), soit Retainable les paramètres de type ne sont pas spécifiés, comme la spécification d'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 concrètes, de types construits fermés ou de types construits ouverts :

// concrete type
class Node : BaseNode
//closed constructed type
class Node : BaseNode
//open constructed type
class Node : BaseNode

// type concret
class Node : BaseNode
//type construit fermé
class Node : BaseNode
//type construit ouvert
class Node : BaseNode

Les classes concrètes non génériques peuvent hériter de classes de base de constructeur fermées, mais pas héritées de la classe de base du constructeur ouvert. En effet, le code client ne peut pas fournir les paramètres de type requis par la classe de base :

//No error.
class Node : BaseNode
//Generates an error.
class Node : BaseNode

table>Les classes concrètes génériques peuvent hériter de types construits ouverts. À l'exception des paramètres de type partagés avec les sous-classes, le type doit être spécifié pour tous les paramètres de type :

// Aucune erreur.
class Node : BaseNode
//Génère une erreur.
class Node : BaseNode

//Generates an error.
class Node : BaseNode {…}
//Okay.
class Node : BaseNode {…}

/ /Génère une erreur.
class Node : BaseNode {…>
//OK.
class Node : BaseNode >

class NodeItem where T : IComparable, new() {…}
class MyNodeItem : NodeItem where T : IComparable, new() {…}

Une classe générique héritée d'un type de structure ouverte, les types de paramètres et les contraintes doivent être spécifiés :

class NodeItem où T : IComparable, new() {…}
class MyNodeItem : NodeItem où T : IComparable, new() {…}
>

class KeyType {…}
class SuperKeyType where U : IComparable, where V : new() {…}

Les types génériques peuvent utiliser une variété de paramètres et de contraintes de type :
class KeyType< ;K, V> {…}
class SuperKeyType où U : IComparable, où V : new() {…}
>

Les types construits ouverts et fermés peuvent être utilisés comme arguments des méthodes :

void Swap(List list1, List list2) {…}
void Swap(List list1, List list2) {…}

void Swap(Liste liste1, Liste liste2) {…>
void Swap(Liste liste1, Liste liste2) {…}

6. Interface générique

Lorsqu'une interface est spécifiée comme contrainte d'un paramètre de type, seule l'interface est spécifiée. qui implémente les types d'interface peut être utilisé comme paramètres de type.

Vous pouvez spécifier plusieurs interfaces comme contraintes sur un type, comme suit :

class Stack where T : IComparable, IMyStack1{}

class Stack où T : IComparable, IMyStack1{}

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

IDictionary

IDictionary

//Okay.
IMyInterface: IBaseInterface
//Okay.
IMyInterface : IBaseInterface
//Okay.
IMyInterface: IBaseInterface
//Error.
IMyInterface : IBaseInterface2

Les règles d'héritage des interfaces et des classes sont les mêmes :

//OK.
IMyInterface : IBaseInterface
//OK.
IMyInterface : IBaseInterface ;
//OK.
IMyInterface : IBaseInterface
//Erreur.
IMyInterface : IBaseInterface2
>

class MyClass : IBaseInterface

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

class MyClass : IBaseInterface

//Okay.
class MyClass : IBaseInterface
//Okay.
class MyClass : IBaseInterface

Classes génériques peut implémenter une interface générique ou une interface construite fermée, à condition que la liste des paramètres de la classe fournisse tous les paramètres requis par l'interface, comme suit :

//OK.
class MyClass : IBaseInterface
//OK.
class MyClass , string>

Les classes génériques, les structures génériques et les interfaces génériques ont toutes les mêmes règles de surcharge de méthodes.

void Swap(ref T lhs, ref T rhs)
{
     T temp;
     temp = lhs;
     lhs = rhs;
     rhs = temp;
}

7. Méthodes génériques

Les méthodes génériques sont des méthodes qui déclarent des paramètres de type, comme suit :

int a = 1;
int b = 2;
//…
Swap(a, b);

void Swap(réf T lhs, réf T rhs)
{
T temp;
temp = ghs;
ghs = rhs;
rhs = temp;
}

L'exemple de code suivant montre un exemple d'appel d'une méthode avec int comme paramètre de type :
Swap(a, b);

int a = 1;
int b = 2;
//…
Échange(a, b);

Vous pouvez également ignorer le paramètre type et le compilateur le déduira. Le code suivant pour appeler Swap est équivalent à l'exemple ci-dessus :

class List
{
     void Swap(ref T lhs, ref T rhs) { ... }
}

Les méthodes statiques et les méthodes d'instance ont les mêmes règles d'inférence de type. Le compilateur est capable de déduire des paramètres de type en fonction des paramètres de méthode transmis ; il ne peut pas être déterminé en fonction de contraintes ou de valeurs de retour uniquement. Par conséquent, l’inférence de type n’est pas valide pour les méthodes sans paramètres. L'inférence de type se produit au moment de la compilation, avant que le compilateur ne résolve les indicateurs de méthode surchargés. Le compilateur applique la logique d'inférence de type à toutes les méthodes génériques portant le même nom. Pendant la phase de résolution de surcharge, le compilateur inclut uniquement les classes génériques pour lesquelles l’inférence de type a réussi.

class List
{
     void Swap(ref T lhs, ref T rhs) {  }
}

warning CS0693: 类型参数“T”与外部类型“List”中的类型参数同名

Dans une classe générique, les méthodes non génériques peuvent accéder aux paramètres de type dans la classe : class List
{
  void Swap(ref T lhs, ref T rhs) { ... }
}
Dans une classe générique, définissez une méthode générique avec les mêmes paramètres de type que la classe dans laquelle elle se trouve ; , le compilateur générera l'avertissement CS0693.
Liste des classes
{
     void Swap (ref T lhs, ref T rhs) { }
}
avertissement CS0693 : le paramètre de type "T" a le même nom qu'un paramètre de type dans le type externe "List "

Dans une classe générique, définir une méthode générique pour définir un paramètre de type non défini dans la classe générique : (peu couramment utilisé, généralement utilisé avec des contraintes)

tbody>

class List
{
     void Swap(ref T lhs, ref T rhs) {  }   //不常用

    void Add(List items) where U : T{}  //常用
}

class List
{
  void Swap(ref T lhs, ref T rhs) { } //Non couramment utilisé

void Add(List items) où U : T{} //Couramment utilisé
}

void DoSomething() { }
void DoSomething() { }
void DoSomething() { }

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

table>

8. Le mot-clé par défaut dans les génériques

  1. Un problème qui se posera dans les classes génériques et les méthodes génériques est de savoir comment définir la valeur par défaut lors de l'affectation à un paramètre type, les deux points suivants ne peuvent pas être connus à l'avance :

  2. T sera un type valeur ou un type référence

Si T est un type valeur, alors T sera une valeur numérique ou une structure

void DoSomething() { >
void DoSomething() { }
void DoSomething() { }

class GenericClass
{
     T GetElement()
     {
         return default(T);
     }
}
Pour une variable t de type paramétré T, L'instruction t = null n'est légale que lorsque T est un type référence ; t = 0 n'est valable que pour les valeurs, pas pour les structures. La solution à ce problème consiste à utiliser le mot-clé par défaut, qui renvoie null pour les types référence et zéro pour les types valeur. Pour les structures, il renverra chaque membre de la structure et renverra zéro ou null selon que le membre est un type valeur ou un type référence.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn