Declaring Generics

From RAD Studio
Jump to: navigation, search

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.

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.

See Also

Retrieved from "https://docwiki.embarcadero.com/RADStudio/Tokyo/e/index.php?title=Declaring_Generics&oldid=251706"