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 // A generic class
type
TBar = 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
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;
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, record, and array 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;
type
anArray<T>= array of T;
IntArray= anArray<integer>;
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.
Parameterized Methods
Methods can be declared with type parameters. Parameter types and result types can use type parameters. However, constructors and destructors cannot have type parameters, and neither can virtual, dynamic, or message methods. Parameterized methods are similar to overloaded methods.
There are two ways to instantiate a method:
- Explicitly specifying type argument
- Automatically inferring from the type argument
For example:
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.
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.