Déclaration des génériques
Remonter à Génériques - Index
La déclaration d'un générique est similaire à la déclaration d'un type de classe, d'enregistrement ou d'interface normal. La différence réside dans le fait qu'un ou plusieurs paramètres de type placés entre les crochets angulaires (< et >) suivent l'identificateur de type dans la déclaration d'un générique.
Un paramètre de type peut être utilisé comme un identificateur de type typique dans un corps de méthode et une déclaration de type de conteneur.
Par exemple :
type
TPair<TKey,TValue> = class // TKey and TValue are type parameters
FKey: TKey;
FValue: TValue;
function GetValue: TValue;
end;
function TPair<TKey,TValue>.GetValue: TValue;
begin
Result := FValue;
end;
Sommaire
Argument de type
Les types génériques sont instanciés en fournissant des arguments de type. Dans Delphi, vous pouvez utiliser n'importe quel type comme argument de type, à l'exception des types suivants : un tableau statique, une chaîne courte ou un type d'enregistrement qui contient (de façon récursive) un champ d'un ou de plusieurs de ces deux types.
type
TFoo<T> = class
FData: T;
end;
var
F: TFoo<Integer>; // 'Integer' is the type argument of TFoo<T>
begin
...
end.
Types imbriqués
Un type imbriqué dans un générique est lui-même un générique.
type
TFoo<T> = class // A generic class
type
TBar = class
X: Integer;
end;
end;
Pour accéder au type imbriqué TBar, vous devez spécifier une construction du premier type TFoo :
var
N: TFoo<Double>.TBar;
Un générique peut également être déclaré dans une classe normale en tant que type imbriqué :
type
TBaz = class // A regular class
type
TQux<T> = class
X: Integer;
end;
end;
type
TOuter = class
type
TData<T> = class
FFoo1: TFoo<Integer>; // declared with closed constructed type
FFoo2: TFoo<T>; // declared with open constructed type
FFooBar1: TFoo<Integer>.TBar; // declared with closed constructed type
FFooBar2: TFoo<T>.TBar; // declared with open constructed type
FBazQux1: TBaz.TQux<Integer>; // declared with closed constructed type
FBazQux2: TBaz.TQux<T>; // declared with open constructed type
...
end;
var
FIntegerData: TData<Integer>;
FStringData: TData<String>;
end;
Types de base
Le type de base d'un type de classe ou d'interface paramétré peut être un type réel ou un type construit. Le type de base ne peut pas être un paramètre de type seul.
type
TFoo1<T> = class(TBar) // Actual type
end;
TFoo2<T> = class(TBar2<T>) // Open constructed type
end;
TFoo3<T> = class(TBar3<Integer>) // Closed constructed type
end;
Si TFoo2<String> est instancié, une classe ancêtre devient TBar2<String> et TBar2<String> est automatiquement instancié.
Types de classe, d'interface et d'enregistrement
Les types de classe, d'interface, d'enregistrement et de tableau peuvent être déclarés avec des paramètres de type.
Par exemple :
type
TRecord<T> = record
FData: T;
end;
type
IAncestor<T> = interface
function GetRecord: TRecord<T>;
end;
IFoo<T> = interface(IAncestor<T>)
procedure AMethod(Param: T);
end;
type
TFoo<T> = class(TObject, IFoo<T>)
FField: TRecord<T>;
procedure AMethod(Param: T);
function GetRecord: TRecord<T>;
end;
type
anArray<T>= array of T;
IntArray= anArray<integer>;
Types procéduraux
Le type de procédure et le pointeur de méthode peuvent être déclarés avec des paramètres de type. Les types de paramètre et les types de résultat peuvent également utiliser des paramètres de type.
Par exemple :
type
TMyProc<T> = procedure(Param: T);
TMyProc2<Y> = procedure(Param1, Param2: Y) of object;
type
TFoo = class
procedure Test;
procedure MyProc(X, Y: Integer);
end;
procedure Sample(Param: Integer);
begin
Writeln(Param);
end;
procedure TFoo.MyProc(X, Y: Integer);
begin
Writeln('X:', X, ', Y:', Y);
end;
procedure TFoo.Test;
var
X: TMyProc<Integer>;
Y: TMyProc2<Integer>;
begin
X := Sample;
X(10);
Y := MyProc;
Y(20, 30);
end;
var
F: TFoo;
begin
F := TFoo.Create;
F.Test;
F.Free;
end.
Méthodes paramétrées
Les méthodes peuvent être déclarées avec des paramètres de type. Les types de paramètre et les types de résultat peuvent utiliser des paramètres de type. Cependant, les constructeurs et les destructeurs ne possèdent pas de paramètres de types, et ni les uns ni les autres ne peuvent avoir de méthodes virtuelles, dynamiques ou de message. Les méthodes paramétrées sont semblables à des méthodes surchargées.
Il existe deux façons d'instancier une méthode :
- Spécifier explicitement l'argument de type
- Déduire automatiquement de l'argument de type
Par exemple :
type
TFoo = class
procedure Test;
procedure CompareAndPrintResult<T>(X, Y: T);
end;
procedure TFoo.CompareAndPrintResult<T>(X, Y: T);
var
Comparer : IComparer<T>;
begin
Comparer := TComparer<T>.Default;
if Comparer.Compare(X, Y) = 0 then
WriteLn('Both members compare as equal')
else
WriteLn('Members do not compare as equal');
end;
procedure TFoo.Test;
begin
CompareAndPrintResult<String>('Hello', 'World');
CompareAndPrintResult('Hello', 'Hello');
CompareAndPrintResult<Integer>(20, 20);
CompareAndPrintResult(10, 20);
end;
var
F: TFoo;
begin
F := TFoo.Create;
F.Test;
ReadLn;
F.Free;
end.
Portée des paramètres de type
La portée d'un paramètre de type couvre la déclaration de type et les corps de tous ses membres, mais n'inclut pas les types descendants.
Par exemple :
type
TFoo<T> = class
X: T;
end;
TBar<S> = class(TFoo<S>)
Y: T; // error! unknown identifier "T"
end;
var
F: TFoo<Integer>;
begin
F.T // error! unknown identifier "T"
end.