Declaring Generics
Go Up to Generics Index
The declaration of a generic is similar to the declaration of a regular class, record, or interface type. The difference is that a list of one or more type parameters placed between angle brackets (< and >) follows the type identifier in the declaration of a generic.
A type parameter can be used as a typical type identifier inside a container type declaration and method body.
For example:
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;
Contents |
Type Argument
Generic types are instantiated by providing type arguments. In Delphi, you can use any type as a type argument except for the following: a static array, a short string, or a record type that (recursively) contains a field of one or more of these two types.
type
TFoo<T> = class
FData: T;
end;
var
F: TFoo<Integer>; // 'Integer' is the type argument of TFoo<T>
begin
...
end.
Nested Types
A nested type within a generic is itself a generic.
type
TFoo<T> = class
type
TBar = class
X: Integer;
// ...
end;
// ...
TBaz = class
type
TQux<T> = class
X: Integer;
// ...
end;
// ...
end;
To access the TBar nested type, you must specify a construction of the TFoo type first:
var
N: TFoo<Double>.TBar;
A generic can also be declared within a regular class as a nested type:
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;
Base Types
The base type of a parameterized class or interface type might be an actual type or a constructed type. The base type might not be a type parameter alone.
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;
If TFoo2<String> is instantiated, an ancestor class becomes TBar2<String>, and TBar2<String> is automatically instantiated.
Class, Interface, and Record Types
Class, interface, and record types can be declared with type parameters.
For example:
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;
Procedural Types
The procedure type and the method pointer can be declared with type parameters. Parameter types and result types can also use type parameters.
For example:
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.
var
F: TFoo;
begin
F := TFoo.Create;
F.Test;
F.Free;
end.
Parameterized Methods
Methods can be declared with type parameters. Parameter types and result types can use type parameters. Parameterized methods are similar to overloaded methods.
There are two ways to instantiate a method:
- Explicitly specifying type argument
- Automatically inferring from the argument type
For example:
type
TMyProc2<Y> = procedure(Param1, Param2: Y) of object;
TFoo = class
procedure Test;
procedure MyProc2<T>(X, Y: T);
end;
procedure TFoo.MyProc2<T>(X, Y: T);
begin
Write('MyProc2<T>');
{$IFDEF CIL}
Write(X.ToString);
Write(', ');
WriteLn(Y.ToString);
{$ENDIF}
WR
end;
procedure TFoo.Test;
var
P: TMyProc2<Integer>;
begin
MyProc2<String>('Hello', 'World'); //type specified
MyProc2('Hello', 'World'); //inferred from argument type
MyProc2<Integer>(10, 20);
MyProc2(10, 20);
P := MyProc2<Integer>;
P(40, 50);
end;
var
F: TFoo;
begin
F := TFoo.Create;
F.Test;
F.Free;
end.
Scope of Type Parameters
The scope of a type parameter covers the type declaration and the bodies of all its members, but does not include descendent types.
For example:
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.