Declaring Generics

From Appmethod Topics
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 Object Pascal, 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.
 
       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
   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 type argument 
             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