Contraintes dans les génériques

De RAD Studio
Aller à : navigation, rechercher

Remonter à Génériques - Index

Les contraintes peuvent être associées à un paramètre de type d'un générique. Les contraintes déclarent des éléments qui doivent être supportés par un type concret transmis à ce paramètre dans une construction du type générique.

Spécification de types paramétrés avec des contraintes

Les éléments de contrainte incluent :

  • Zéro, un ou plusieurs types d'interface
  • Zéro ou un type de classe
  • Le mot réservé "constructor", "class" ou "record"

Vous pouvez spécifier "constructor" et "class" pour une contrainte. Toutefois, "record" ne peut pas être combiné à d'autres mots réservés. Plusieurs contraintes agissent comme une union additive ("AND" logique)

Les exemples fournis ici ne montrent que les types de classe, bien que les contraintes s'appliquent à toutes les formes de types paramétrés

Déclaration des contraintes

Les contraintes sont déclarées d'une manière qui ressemble aux déclarations de type dans les listes de paramètres habituelles :

 type
   TFoo<T: ISerializable> = class
     FField: T;
   end;

Dans la déclaration exemple donnée ici, le paramètre de type 'T' indique qu'il doit supporter l'interface ISerializable. Dans une construction type comme TFoo<TMyClass>, le compilateur vérifie à la compilation que TMyClass implémente réellement ISerializable.

Paramètres de type multiples

Lorsque vous spécifiez des contraintes, vous séparez plusieurs paramètres de type par des points-virgules, comme vous le faites avec une déclaration de liste de paramètres :

 type
    TFoo<T: ISerializable; V: IComparable>

Comme les déclarations de paramètres, plusieurs paramètres de type peuvent être regroupés ensemble dans une liste à virgules pour une liaison sur les mêmes contraintes :

 type
    TFoo<S, U: ISerializable> ...

Dans l'exemple ci-dessus, S et U sont tous deux liés à la contrainte ISerializable.

Contraintes multiples

Plusieurs contraintes peuvent être appliquées aux paramètres d'un type unique avec une liste à virgules suivant le deux-points :

 type
    TFoo<T: ISerializable, ICloneable; V: IComparable> ...

Les paramètres de type avec contraintes peuvent être mélangés à des paramètres de type "libres". Par exemple, ce qui suit est valide :

 type
    TFoo<T; C: IComparable> ...
    TBar<T, V> ...
    TTest<S: ISerializable; V> ...
    // T and V are free, but C and S are constrained

Types de contraintes

Contraintes de type d'interface

Lorsque vous spécifiez des contraintes, vous séparez plusieurs paramètres de type par des points-virgules, comme vous le faites avec une déclaration de liste de paramètres :

Un paramètre de type avec contrainte soumise par un type d'interface signifie que le compilateur vérifiera à la compilation qu'un type concret transmis comme argument à une construction de type implémente le(s) type(s) d'interface spécifié(s).

Par exemple :

 type
   TFoo<T: ICloneable> ...
 
   TTest1 = class(TObject, ICloneable)
      ...
   end;
 
   TError = class
   end;
 
 var
   X: TFoo<TTest1>;  //  TTest1 is checked for ICloneable support here
                     //  at compile time
   Y: TFoo<TError>;  //  exp: syntax error here - TError does not support
                     //  ICloneable

Contraintes de type de classe

Un paramètre de type peut être contraint par zéro ou un type de classe. Comme avec les contraintes de type d'interface, cette déclaration signifie que le compilateur nécessitera que tout type concret transmis comme argument au paramètre de type contraint soit compatible en affectation avec la classe de contrainte.

La compatibilité des types de classe suit les règles normales de la compatibilité de type OOP - les types descendants peuvent être transmis là où leurs types ancêtres sont requis.

Contraintes de constructeur

Un paramètre de type peut être contraint par zéro ou une instance du mot réservé "constructor". Cela signifie que le type d'argument réel doit être une classe qui définit un constructeur par défaut (constructeur sans paramètre public), de telle sorte que les méthodes d'un type générique peuvent construire des instances du type d'argument à l'aide du constructeur par défaut du type d'argument, sans ne rien savoir du type d'argument lui-même (pas d'exigences de type de base minimum).

Dans une déclaration de contrainte, vous pouvez mélanger le mot réservé "constructor" dans n'importe quel ordre avec les contraintes de type de classe ou d'interface.

Contrainte de classe

Les paramètres de type avec contraintes peuvent être mélangés à des paramètres de type "libres". Par exemple, ce qui suit est valide :

Contrainte d'enregistrement

Un paramètre de type peut être contraint par zéro ou une instance du mot réservé "record". Cela signifie que le type réel doit être un type de valeur (pas un type de référence). Une contrainte "record" ne peut pas être combinée à une contrainte "class" ou "constructor".

Déduction du type

Lors de l'utilisation d'un champ ou d'une variable d'un paramètre de type contraint, il n'est pas nécessaire dans la plupart des cas de transtyper afin de traiter le champ ou la variable comme un des types contraints. Le compilateur peut déduire le type auquel vous faites référence en recherchant le nom de la méthode et en effectuant une variation de la résolution de surcharge sur l'union des méthodes partageant le même nom à travers toutes les contraintes sur ce type.

Par exemple :

 type
   TFoo<T: ISerializable, ICloneable> = class
     FData: T;
     procedure Test;
   end;
 
 procedure TFoo<T>.Test;
 begin
   FData.Clone;
 end;

Le compilateur recherche les méthodes "Clone" dans ISerializable et IClonable, puisque FData est de type T, ce qui garantit le support des deux interfaces. Si les deux interfaces implémentent "Clone" avec la même liste de paramètres, le compilateur émet une erreur d'appel de méthode ambigu et requiert que vous transtypiez vers l'une ou l'autre interface afin de lever l'ambiguité du contexte.

Voir aussi