Contraintes dans les génériques
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.
Sommaire
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.