Contraintes sur les paramètres de type générique (C++/CLI)

Dans les déclarations de type ou de méthode génériques, vous pouvez qualifier un paramètre de type avec des contraintes. Une contrainte est une exigence que les types utilisés comme arguments de type doivent respecter. Par exemple, une contrainte peut indiquer que l'argument de type doit implémenter une certaine interface ou hériter d'une classe spécifique.

Les contraintes sont facultatives ; ne pas spécifier de contrainte sur un paramètre équivaut à utiliser une Object contrainte.

Syntaxe

where type-parameter : constraint-list

Paramètres

type-parameter
Un des identificateurs de paramètre de type à limiter.

constraint-list
Liste séparée par des virgules des spécifications de contrainte. La liste peut inclure des interfaces à implémenter par le type-parameter.

La liste peut également inclure une classe. Pour satisfaire une contrainte de classe de base, l’argument de type doit être la même classe que la contrainte ou dériver de la contrainte. Spécifiez ref class pour indiquer que l’argument de type doit être un type référence, y compris n’importe quel class, interface, delegateou array type. Spécifiez value class pour indiquer que l’argument de type doit être un type valeur. Tout type valeur, excepté Nullable<T>, peut être spécifié.

Vous pouvez également spécifier gcnew() pour indiquer que l’argument de type doit avoir un constructeur sans paramètre public.

Vous pouvez également spécifier un paramètre générique en tant que contrainte. L’argument de type fourni pour le type que vous limitez doit être ou dériver du type de la contrainte. Ce paramètre est appelé contrainte de type nu.

Notes

La clause de contrainte se compose d’un where paramètre de type, d’un signe deux-points (:) et de la contrainte, qui spécifie la nature de la restriction sur le paramètre de type. where est un mot clé contextuel. Pour plus d’informations, consultez les mot clé sensibles au contexte. Séparez plusieurs where clauses par un espace.

Les contraintes sont appliquées aux paramètres de type pour placer des restrictions sur les types qui peuvent être utilisés comme arguments pour un type générique ou une méthode générique.

La classe et les contraintes d’interface spécifient les types d’argument qui doivent être, ou hériter, d’une classe spécifiée ou implémentent une interface spécifiée.

L’application des contraintes sur un type ou une méthode générique permet au code de ce type ou de cette méthode de tirer parti des fonctionnalités connues des types de contraintes. Vous pouvez, par exemple, déclarer une classe générique, de telle sorte que le paramètre de type implémente l’interface IComparable<T> :

// generics_constraints_1.cpp
// compile with: /c /clr
using namespace System;
generic <typename T>
where T : IComparable<T>
ref class List {};

Cette contrainte exige qu’un argument de type utilisé pour T implémente IComparable<T> au moment de la compilation. Il permet également aux méthodes de l’interface, telles que CompareTo, d’être appelées. Aucune conversion n'est nécessaire sur une instance du paramètre de type pour appeler les méthodes d'interface.

Les méthodes statiques dans la classe de l’argument de type ne peuvent pas être appelées par le biais du paramètre de type ; ils peuvent être appelés uniquement par le type nommé réel.

Une contrainte ne peut pas être un type valeur, y compris des types intégrés tels que int ou double. Étant donné que les types valeur ne peuvent pas avoir de classes dérivées, une seule classe ne peut jamais satisfaire la contrainte. Dans ce cas, le générique peut être réécrit avec le paramètre de type remplacé par le type de valeur spécifique.

Les contraintes sont requises dans certains cas, car le compilateur n’autorise pas l’utilisation de méthodes ou d’autres fonctionnalités d’un type inconnu, sauf si les contraintes impliquent que le type inconnu prend en charge les méthodes ou interfaces.

Plusieurs contraintes pour le même paramètre de type peuvent être spécifiés dans une liste séparée par des virgules

// generics_constraints_2.cpp
// compile with: /c /clr
using namespace System;
using namespace System::Collections::Generic;
generic <typename T>
where T : List<T>, IComparable<T>
ref class List {};

Avec plusieurs paramètres de type, utilisez une clause where pour chaque paramètre de type. Par exemple :

// generics_constraints_3.cpp
// compile with: /c /clr
using namespace System;
using namespace System::Collections::Generic;

generic <typename K, typename V>
   where K: IComparable<K>
   where V: IComparable<K>
ref class Dictionary {};

Utilisez des contraintes dans votre code en fonction des règles suivantes :

  • Si plusieurs contraintes sont répertoriées, les contraintes peuvent être répertoriées dans un ordre quelconque.

  • Les contraintes peuvent être également des types de classe, tels que les classes de base abstraites. Toutefois, les contraintes ne peuvent pas être des types ou sealed des classes de valeur.

  • Les contraintes ne peuvent pas elles-mêmes être des paramètres de type, mais elles peuvent impliquer les paramètres de type dans un type construit ouvert. Par exemple :

    // generics_constraints_4.cpp
    // compile with: /c /clr
    generic <typename T>
    ref class G1 {};
    
    generic <typename Type1, typename Type2>
    where Type1 : G1<Type2>   // OK, G1 takes one type parameter
    ref class G2{};
    

Exemples

L'exemple suivant illustre l'utilisation de contraintes pour appeler des méthodes d'instance sur des paramètres de type.

// generics_constraints_5.cpp
// compile with: /clr
using namespace System;

interface class IAge {
   int Age();
};

ref class MyClass {
public:
   generic <class ItemType> where ItemType : IAge
   bool isSenior(ItemType item) {
      // Because of the constraint,
      // the Age method can be called on ItemType.
      if (item->Age() >= 65)
         return true;
      else
         return false;
   }
};

ref class Senior : IAge {
public:
   virtual int Age() {
      return 70;
   }
};

ref class Adult: IAge {
public:
   virtual int Age() {
      return 30;
   }
};

int main() {
   MyClass^ ageGuess = gcnew MyClass();
   Adult^ parent = gcnew Adult();
   Senior^ grandfather = gcnew Senior();

   if (ageGuess->isSenior<Adult^>(parent))
      Console::WriteLine("\"parent\" is a senior");
   else
      Console::WriteLine("\"parent\" is not a senior");

   if (ageGuess->isSenior<Senior^>(grandfather))
      Console::WriteLine("\"grandfather\" is a senior");
   else
      Console::WriteLine("\"grandfather\" is not a senior");
}
"parent" is not a senior
"grandfather" is a senior

Lorsqu’un paramètre de type générique est utilisé comme contrainte, il est appelé contrainte de type nu. Les contraintes de type naked sont utiles lorsqu’une fonction membre avec son propre paramètre de type doit limiter ce paramètre au paramètre de type contenant le type.

Dans l’exemple suivant, T est une contrainte de type naked dans le contexte de la méthode Add.

Les contraintes de type naked peuvent être également utilisées dans des définitions de classe générique. L'utilité des contraintes de type naked avec les classes génériques est très limitée, car la compilation ne peut rien deviner à propos des contraintes de types naked en dehors du fait qu'elles dérivent de Object. Utilisez des contraintes de type naked sur les classes génériques dans des scénarios dans lesquels vous souhaitez mettre en application une relation d’héritage entre deux paramètres de type.

// generics_constraints_6.cpp
// compile with: /clr /c
generic <class T>
ref struct List {
   generic <class U>
   where U : T
   void Add(List<U> items)  {}
};

generic <class A, class B, class C>
where A : C
ref struct SampleClass {};

Voir aussi

Génériques