ジェネリックスの制約
ジェネリックス:インデックス への移動
制約は、ジェネリック型の型パラメータに関連付けできます。制約は、ジェネリック型の構文のパラメータに渡される特定の型がサポートしなければならない項目を宣言します。
目次
制約のあるジェネリック型の指定
制約項目には以下のものが含まれます。
- 0、1、または複数のインターフェイス型
- 0 または 1 のクラス型
- 予約語 "constructor"、"class"、または "record"
1 つの制約について "constructor" と "class" の両方を指定できます。 これに対し、"record" は他の予約語と組み合わせできません。 複数の制約は、積("AND")として作用します。
制約はすべての形式のジェネリック型に適用されます。ここの例ではクラス型のみを示します。
制約の宣言
制約の宣言は、通常のパラメータ リストの型宣言に類似しています。
type
TFoo<T: ISerializable> = class
FField: T;
end;
この宣言は、'T' 型パラメータが ISerializable インターフェイスをサポートしなければならないことを示しています。 TFoo<TMyClass> のような型構文では、コンパイラはコンパイル時に確認を行って、TMyClass が ISerializable を確実に実装するようにします。
複数の型パラメータ
制約を指定するときは、パラメータ リストの宣言と同じように、複数の型パラメータをセミコロンで区切ります。
type
TFoo<T: ISerializable; V: IComparable>
パラメータ宣言と同じように、複数の型パラメータをグループ化してコンマ リストにし、同じ制約にバインドできます。
type
TFoo<S, U: ISerializable> ...
上の例では、S と U が両方とも ISerializable 制約にバインドされます。
複数の制約
複数の制約を、コロンに続くコンマ リストとして単一の型パラメータに適用できます。
type
TFoo<T: ISerializable, ICloneable; V: IComparable> ...
制約された型パラメータは、"free" 型パラメータと混在させることができます。 たとえば、以下はすべて有効です。
type
TFoo<T; C: IComparable> ...
TBar<T, V> ...
TTest<S: ISerializable; V> ...
// T and V are free, but C and S are constrained
制約の型
インターフェイス型の制約
型パラメータ制約には、0、1、または複数のインターフェイス型のコンマ区切りリストを組み入れできます。
インターフェイス型で制約された型パラメータがある場合、コンパイラはコンパイル時に確認を行って、構文に引数として渡される具体的な型が指定のインターフェイス型を実装するようにします。
例:
type
TFoo<T: ICloneable> ...
TTest1 = class(TObject, ICloneable)
...
end;
TError = class
end;
var
X: TFoo<TTest1>; // TTest1 is checked for ICloneable support here
// at compile time
Y: TFoo<TError>; // exp: syntax error here - TError does not support
// ICloneable
クラス型制約
型パラメータは 0 または 1 個のクラス型によって制約できます。 インターフェイス型制約と同じように、この宣言がある場合、制約された型パラメータに引数として渡される具体的な型にはどの制約クラスとも代入互換性があることが、コンパイラで要求されます。
クラス型の互換性は、OOP 型互換性の通常のルールに従います。つまり、上位の型が要求される場合に下位の型を渡せます。
コンストラクタ制約
型パラメータは、予約語 "constructor" の 0 または 1 個のインスタンスによって制約できます。 つまり、実際の引数型は、デフォルトのコンストラクタ(public なパラメータのないコンストラクタ)を定義するクラスである必要があります。その結果、ジェネリック型のメソッドは、引数型のデフォルトのコンストラクタを使用して引数型のインスタンスを作成できます。引数型については何も認識する必要はありません(最小基底型の要件なし)。
制約宣言では、インターフェイス型制約またはクラス型制約とともにどのような順序でも "constructor" を混在させられます。
クラス制約
型パラメータは、予約語 "class" の 0 または 1 個のインスタンスによって制約できます。 つまり、実際の型はクラス型でなければなりません。
レコード制約
型パラメータは、予約語 "record" の 0 または 1 個のインスタンスによって制約できます。 つまり、実際の型は値型でなければなりません(参照型ではありません)。 "record" 制約は、"class" 制約または "constructor" 制約とは組み合わせできません。
型の推論
制約された型パラメータのフィールドまたは変数を使用するとき、多くの場合、フィールドまたは変数を制約された型の 1 つとして取り扱うために型キャストする必要はありません。 コンパイラはどの型が参照されているかを推論できます。そのために、コンパイラは、メソッド名を調べ、その型に対する全制約について同じ名前を共有するメソッドの結合についてさまざまなオーバーロード解決を実行します。
例:
type
TFoo<T: ISerializable, ICloneable> = class
FData: T;
procedure Test;
end;
procedure TFoo<T>.Test;
begin
FData.Clone;
end;
コンパイラは ISerializable と ICloneable で "Clone" メソッドを探します。FData は型 T だからです。型 T はこれら両方のインターフェイスをサポートすることが保証されています。 両方のインターフェイスが同じパラメータ リストで "Clone" を実装する場合、コンパイラは、メソッド呼び出しがあいまいであるというエラーを出し、一方または他方のインターフェイスを型キャストしてコンテキストからあいまいさを除去するよう要求します。