ジェネリックスの宣言
ジェネリックス:インデックス への移動
ジェネリック宣言は、通常のクラス型、レコード型、インターフェイス型の宣言と似ています。違いは、ジェネリック宣言では、1 つ以上の型パラメータから成るリストを不等号かっこ(< と >)で囲んだものが型識別子の後に続く点です。
型パラメータは、コンテナ型の宣言およびメソッド本体内部で典型的な型識別子として使用することができます。
次に例を示します。
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;
型引数
ジェネリック型のインスタンス化は、型引数を指定して行われます。Delphi では、静的配列、短い文字列、これら 2 つの型のどちらかを型とするフィールドを 1 つ以上(場合によっては再帰的に)含むレコード型を除く任意の型を型引数として使用できます。
type
TFoo<T> = class
FData: T;
end;
var
F: TFoo<Integer>; // 'Integer' is the type argument of TFoo<T>
begin
...
end.
ネスト型
ジェネリック型内部のネスト型は、それ自体がジェネリック型となります。
type
TFoo<T> = class // A generic class
type
TBar = class
X: Integer;
end;
end;
ネスト型 TBar にアクセスするには、次のように、TFoo 型のインスタンスをまず指定する必要があります。
var
N: TFoo<Double>.TBar;
ジェネリック型は、次のように、通常のクラスの内部でネスト型として宣言することもできます。
type
TBaz = class // A regular class
type
TQux<T> = class
X: Integer;
end;
end;
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;
基底型
パラメータ化クラス/インターフェイス型の基底型は、実際の型または生成型である可能性があります。基底型は型パラメータ単独ではないことがあります。
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;
TFoo2<String> がインスタンス化されると、上位クラスが TBar2<String> になり、TBar2<String> は自動的にインスタンス化されます。
クラス型、インターフェイス型、レコード型
クラス型、インターフェイス型、レコード型、配列型は、型パラメータを付けて宣言することができます。
次に例を示します。
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>;
手続き型
手続き型とメソッド ポインタは型パラメータを付けて宣言することができます。パラメータ型と結果型でも型パラメータを使用できます。
次に例を示します。
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.
パラメータ化メソッド
メソッドは、型パラメータを付けて宣言することができます。パラメータ型と結果型で型パラメータを使用することができます。ただし、コンストラクタとデストラクタは型パラメータを持てず、virtual、dynamic、message の各修飾子の付いたメソッドも型パラメータを持てません。パラメータ化メソッドはオーバーロード メソッドと似ています。
メソッドをインスタンス化する方法は次の 2 とおりあります。
- 型引数の明示的な指定
- 型引数からの自動的な推論
次に例を示します。
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.
型パラメータのスコープ
型パラメータのスコープには、型宣言とそのすべてのメンバの本体が含まれますが、下位の型は含まれません。
次に例を示します。
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.