Deklaration von Generics

Aus Appmethod Topics
Wechseln zu: Navigation, Suche

Nach oben zu Generics - Index

Die Deklaration von Generics ist vergleichbar mit der Deklaration von regulären Klassen-, Record- oder Interface-Typen. Der Unterschied besteht darin, dass die Deklaration von Generics eine Liste mit einem oder mehreren Typparametern in spitzen Klammern (< und >) nach dem Typbezeichner enthält.

Ein Typparameter kann als typischer Typbezeichner innerhalb der Container-Typdeklaration und im Methodenrumpf verwendet werden.

Zum Beispiel:

 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;

Hinweis: Vor dem Aufruf der Methode GetValue müssen Sie den Standardkonstruktor aufrufen und die Klassenfelder initialisieren.

Typargument

Generische Typen werden durch das Bereitstellen von Typargumenten instantiiert. In Object Pascal können Sie jeden beliebigen Typ außer den folgenden als Typargument verwenden: ein statisches Array, ein Short String oder ein Record-Typ, der ein Feld mit einem oder mehreren dieser beiden Typen (rekursiv) enthält.

 type
   TFoo<T> = class
     FData: T;
   end;
 var
   F: TFoo<Integer>; // 'Integer' is the type argument of TFoo<T>
 begin
   ...
 end.

Verschachtelte Typen

Ein in einem generischen Typ verschachtelter Typ ist selbst ein generischer Typ.

type
  TFoo<T> = class
  type
    TBar = class
      X: Integer;
      // ...
    end;
  end;

  // ...
  TBaz = class
  type
    TQux<T> = class
      X: Integer;
      // ...
    end;
    // ...
  end;

Um auf den verschachtelten Typ TBar zuzugreifen, müssen Sie zuerst eine Konstruktion des Typs TFoo angeben:

 var
   N: TFoo<Double>.TBar;

Ein generischer Typ kann auch in einer regulären Klasse als verschachtelter Typ deklariert werden:

 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;

Basistypen

Der Basistyp eines parametrisierten Klassen- oder Interface-Typs kann ein tatsächlicher Typ oder ein konstruierter Typ sein. Der Basistyp darf kein Typparameter sein.

  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;

Wenn TFoo2<String> instantiiert ist, wird eine Vorfahrklasse zu TBar2<String>, und TBar2<String> wird automatisch instantiiert.

Klassen-, Interface- und Record-Typen

Klassen-, Interface-, Record- und Array-Typen können mit Typparametern deklariert werden.

Zum Beispiel:

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

Prozedurale Typen

Prozedurale Typen und Methodenzeiger können mit Typparametern deklariert werden. Parametertypen und Ergebnistypen können auch Typparameter verwenden.

Zum Beispiel:

 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.

Parametrisierte Methoden

Methoden können mit Typparametern deklariert werden. Parametertypen und Ergebnistypen können Typparameter verwenden. Konstruktoren und Destruktoren sowie virtuelle, dynamische oder Botschaftsmethoden dürfen allerdings keine Typparameter haben. Parametrisierte Methoden sind mit überladenen Methoden vergleichbar.

Es gibt zwei Möglichkeiten, eine Methode zu instantiieren:

  • Durch die explizite Angabe eines Typarguments
  • Durch automatische Ableitung vom Typargument

Zum Beispiel:

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.

Gültigkeitsbereich von Typparametern

Der Gültigkeitsbereich eines Typparameters umfasst die Typdeklaration und den Rumpf aller Member, aber keine abgeleiteten Typen.

Zum Beispiel:

 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.

Siehe auch