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;

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;
     // ...  
     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, 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;

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.

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

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

Personal tools
Newest Version: XE
In other languages