RAD Studio (Common)
ContentsIndex
PreviousUpNext
Declaring Generics

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.  

Type parameters can be used as a typical type identifier inside the 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;

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.

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;

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 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;

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.

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.

The scope of a type parameter covers the type declaration and the bodies of all its members, but does not include descendant 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.
Copyright(C) 2009 Embarcadero Technologies, Inc. All Rights Reserved.
What do you think about this topic? Send feedback!