2012年12月3日

ジェネリックスの型パラメータに対する制約

このアーティクルはDelphi Advent Calendar 2012に参加しています。

ジェネリックスでは渡される型パラメータに関する制約を指定することができます。

ジェネリックスの制約 - RAD Studio XE3

ここで制約としてインタフェース型またはクラス型を指定すると型パラメータは必然的にそのクラス型、インタフェース型になります。またconstructorを指定すると型パラメータはクラス型になります(レコード型はパラメータを持たないコンストラクタを定義できないため)。classを指定するとこれもまたクラス型、インタフェース型になります(ヘルプ参照)。

では最後に残ったrecordを指定(レコード指定)した場合、型パラメータに許される型はレコード型だけなのでしょうか?実際にはレコード型だけではなく、いわゆる値型(参照型ではないもの)であればいいようで、IntegerやCardinal、あるいは部分範囲型や列挙型も指定することができます。
type
  TConstraintTest<T: record> = record
    class procedure TestProc(Value: Integer); static;
  end;

class procedure TConstraintTest<T>.TestProc(Value: Integer);
begin
  { No operation }
end;
このようなジェネリックスを定義すると、
type
  TTestRange = 1..5;
  TTestEnum = (One, Two, Three);

begin
  TConstraintTest<TPoint>.TestProc(0);
  TConstraintTest<Integer>.TestProc(0);
  TConstraintTest<Cardinal>.TestProc(0);
  TConstraintTest<TTestRange>.TestProc(0);
  TConstraintTest<TTestEnum>.TestProc(0);
end;
これらはすべて正しくコンパイルされます。

ただしString型は参照型でありながらクラス型ではないため、constructor以外の制約を指定された型パラメータにStringを指定するとコンパイルエラーになります("class"を指定するとE2511 型パラメータ 'T' はクラス型が必要です、"record"を指定するとE2512 型パラメータ 'T' は非 null 値型が必要ですとなる)。つまり文字列型を受け入れるためには型パラメータに制約を指定しないか、constructor制約を指定するかのいずれかが必要ということになります。

元ねたはDelphi XE2 Foundations

2013/07/03追記: constructor制約については細川さんによる考察が参考になります。

0 件のコメント: