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 for .NET, 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 sthe type argument of TFoo<T>
begin
   ,,,
end.

A nested type within a parameterized type is itself also a parameterized type.

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 parameterized type 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: TRecrod<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');
  MyProc2('Hello', 'World');
  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 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.
Copyright(C) 2008 CodeGear(TM). All Rights Reserved.
What do you think about this topic? Send feedback!