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;
Note: You must call the default constructor and initialize the class fields before calling the GetValue method.
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; 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, 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.