Delphi におけるジェネリックプログラミング

# はじめに
Delphi のジェネリックプログラミングに関する記事です。

 - [ジェネリックス (DocWiki)](https://docwiki.embarcadero.com/RADStudio/Alexandria/ja/%E3%82%B8%E3%82%A7%E3%83%8D%E3%83%AA%E3%83%83%E3%82%AF%E3%82%B9%EF%BC%9A%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9)
 - [ジェネリックプログラミング (Wikipedia)](https://ja.wikipedia.org/wiki/%E3%82%B8%E3%82%A7%E3%83%8D%E3%83%AA%E3%83%83%E3%82%AF%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0)


## ジェネリックプログラミングとは？

**ジェネリックプログラミング**を簡単に言うと、`型をパラメータ化して型に依らない汎用的なコードを書く` というものです。

例えば、A と B の変数の値を交換する `Swap()` という手続きがあるとしましょう。

```pascal
procedure Swap(var A, B: Integer);
var
  C: Integer;
begin
  C := A;
  A := B;
  B := C;
end;
```

実数型と文字列型の変数も交換したくなりました。

```pascal
procedure Swap(var A, B: Integer); overload;
procedure Swap(var A, B: Real); overload;
procedure Swap(var A, B: String); overload;

...

procedure Swap(var A, B: Integer);
var
  C: Integer;
begin
  C := A;
  A := B;
  B := C;
end;

procedure Swap(var A, B: Real);
var
  C: Real;
begin
  C := A;
  A := B;
  B := C;
end;

procedure Swap(var A, B: String);
var
  C: String;
begin
  C := A;
  A := B;
  B := C;
end;
```

日付時刻型の...と、際限がありません。それぞれの手続きは型にしか違いはないのですから、型をパラメータで仮置き (プレースホルダ) して、使う時に任意の型を指定できるようにすればいいのでは？というのがジェネリックプログラミングの考え方です。

誰ですか？ **「インクルードファイルで書けらぁ！」** なんて言うのは。

```pascal:GenericsTest2.dpr
program GenericsTest2;

{$APPTYPE CONSOLE}

uses
  SysUtils;

procedure Swap(var A, B: Integer); overload;
var
  C: Integer;
{$I Generic.inc}

procedure Swap(var A, B: Real); overload;
var
  C: Real;
{$I Generic.inc}

procedure Swap(var A, B: string); overload;
var
  C: string;
{$I Generic.inc}

begin


end.
```

```pascal: Generic.inc
begin
  C := A;
  A := B;
  B := C;
end;
```

そういう話ではないんですよねぇ。


## Delphi のジェネリックプログラミングの始まり

Delphi におけるジェネリックプログラミングは **Delphi 2007** から行えるようになりました。Delphi 2007 とは言っても **Delphi 2007 for .NET** の方で、for Win32 の方の対応は **Delphi 2009** からとなります。

Delphi に導入されたジェネリックプログラミングの手法は、Delphi が文法の一部を参考にしている **Ada** からでも、Pascal の後継言語である **Modula-2** からでもなく、**C# 2.0** のものを参考にしています。

:::note info
個人的に Delphi の言語拡張は Pascal 派生言語から持ってくるのが原則だと思っていますが [^1]、ジェネリックプログラミングに関しては (Delphi for .NET があったからとはいえ) **C#** を参考にしたのは正解だったと思っています。
:::

**See also:**

 - [Ada (Wikipedia)](https://ja.wikipedia.org/wiki/Ada)
 - [Modula-2 (Wikipedia)](https://ja.wikipedia.org/wiki/Modula-2)
 - [C# (Wikipedia)](https://ja.wikipedia.org/wiki/C_Sharp)

## Delphi にジェネリックプログラミングは必須なのか？
Delphi は静的型付け言語の中でも **ALGOL** の系譜なので特に型にうるさく、ジェネリックプログラミングが可能だと嬉しい場面が多いのは間違いないと思います。

が、**「そんなに型ごとに関数作る場面あるか？」** という疑問も浮かんでくると思います。先程の `Swap()` の例で言うと、Integer と String しか要らないのであれば、確かにコピペしてオーバーロードした方が簡単で早いですし。

ジェネリックプログラミングは[プログラミングパラダイム](https://ja.wikipedia.org/wiki/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%83%91%E3%83%A9%E3%83%80%E3%82%A4%E3%83%A0)の一種なので、無理に使う必要はないですが、知っておいて損はないと思います。

**See also:**

 - [ALGOL (Wikipedia)](https://ja.wikipedia.org/wiki/ALGOL)

# ジェネリックス
Delphi におけるジェネリックプログラミングは基本的に型ベースです。

## ■ クラスのジェネリック

`Swap()` メソッドをクラスメソッドとして実装してみます。

```pascal
type
  TSwap<T> = class
  public
    class procedure Swap(var A, B: T);
  end;

{ TSwap<T> }

class procedure TSwap<T>.Swap(var A, B: T);
var
  C: T;
begin
  C := A;
  A := B;
  B := C;
end;
```

`T` というプレースホルダが**型パラメータ** (Type Parameter) です。型パラメータの名前は別に何でもいいのですが、慣習的に `Type` の頭文字である `T` が使われます。型パラメータを用いて定義された型は**ジェネリック** (Generic) または**ジェネリック型** (Type generic) と呼ばれます [^2]。

使い方は次のようになります。

```pascal
var
  a, b: Integer;
  c, d: string;
begin
  a := 3;
  b := 5;
  TSwap<Integer>.Swap(a, b);
  Writeln('a= ', a);
  Writeln('b= ', b);

  c := 'Hello,';
  d := 'world.';
  TSwap<string>.Swap(c, d);
  Writeln('c= ', c);
  Writeln('d= ', d);
end.
```

`TSwap<Integer>.Swap(a, b);` に指定した `Integer` を**型引数** (Type Argument) と呼びます。

`<>` で括られた型パラメータを**型パラメータリスト** (Type Parameter List) と呼び、必要に応じて複数の型パラメータをカンマ区切りで指定できます。

```pascal
type
  TPair<TKey,TValue> = class
  ...
```

## ■ レコードのジェネリック
ジェネリックはレコード型でも作れます。

```pascal
type
  TSwap<T> = record
  public
    class procedure Swap(var A, B: T); static;
  end;

{ TSwap<T> }

class procedure TSwap<T>.Swap(var A, B: T);
var
  C: T;
begin
  C := A;
  A := B;
  B := C;
end;
```

## ■ 配列のジェネリック
ジェネリックは配列でも作れます。最近の Delphi ですと、汎用動的配列が `TArray<T>` として定義されています。

```pascal:System.pas
TArray<T> = array of T;
```

文字列の動的配列は次のように定義されています。

```pascal:System.Types.pas
TStringDynArray = TArray<string>;
```

### ・ジェネリック配列の代入互換性
Delphi は Pascal 系の言語なので、変数の代入互換性は **名前等価** (Name Equivalence) となっています。名前等価での問題は配列の代入などで見る事ができます。

```pascal
type
  TArr = array [1..10] of string; // ユーザー定義の型
var
  Arr1: TArr;
  Arr2: array [1..10] of string; // ユーザー定義の型
  Arr3: array [1..10] of string; // ユーザー定義の型
begin
  Arr1 := Arr2; // 名前等価なので、代入はできない。Arr1 と Arr2 は別の型とみなされる。
  Arr2 := Arr3; // 名前等価なので、代入はできない。Arr2 と Arr3 は別の型とみなされる。
end.
```

しかしながら、ジェネリック配列においては **構造等価** (Structual Equivalence) のような振る舞いを見せる事があります。

```pascal
type
  TArr<T> = array [1..10] of T; // ユーザー定義の型
  TIntArr = TArr<Integer>;      // ユーザー定義の型 (?)
var
  Arr1: TArr<Integer>; // ユーザー定義の型 (?)
  Arr2: TIntArr;
  Arr3: TIntArr;
begin
  Arr1 := Arr2; // 代入可能。Arr1 と Arr2 は同じ型とみなされる。
  Arr2 := Arr3; // Arr1 と Arr2 は同じ型。
end.
```

ジェネリックと型パラメータを組み合わせたものを **生成型** (Constructed type) と呼び、生成型のうちすべての型パラメータが実際の型であるものを **閉じた生成型** (Closed constructed type)、プレースホルダが一つでも残っているものを **開いた生成型** (Open constructed type) と呼びます。

上記例の `TArr<Integer>` は閉じた生成型ですが、閉じた生成型では型をユーザー定義したとはみなされないようです。

**See also:**

 - [(6.4.4) 構造等価 (Structual Equivalence) と 名前等価 (Name Equivalence) (Qiita)](https://qiita.com/ht_deko/items/eedda6d38b6d0887d4ac#644-%E6%A7%8B%E9%80%A0%E7%AD%89%E4%BE%A1-structual-equivalence-%E3%81%A8-%E5%90%8D%E5%89%8D%E7%AD%89%E4%BE%A1-name-equivalence)
 - [ジェネリックスでのオーバーロードおよび型の互換性 (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%E3%82%B8%E3%82%A7%E3%83%8D%E3%83%AA%E3%83%83%E3%82%AF%E3%82%B9%E3%81%A7%E3%81%AE%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%AD%E3%83%BC%E3%83%89%E3%81%8A%E3%82%88%E3%81%B3%E5%9E%8B%E3%81%AE%E4%BA%92%E6%8F%9B%E6%80%A7)


## ■ その他のジェネリック
その他にジェネリックを定義できる型には次のようなものがあります。

 - 手続き型
 - メソッドポインタ
 - インターフェイス

レコードとクラスではジェネリックを定義できますが、[オブジェクト型](https://qiita.com/ht_deko/items/28fc0af9fc9e34a8d5c5#61-delphi-%E3%81%AE%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E5%9E%8B)でジェネリックを定義する事はできません。

## ■ 型パラメータを使ったメソッド

型そのものではなく、メソッドで型パラメータを使う事ができます。型パラメータ使って宣言されたメソッドを**パラメータ化メソッド** (Parameterized Methods) と呼びます。パラメータ型だけではなく、結果型 (戻り値) にも型パラメータを使う事ができます。

```pascal
type
  TSwap = record
  public
    class procedure Swap<T>(var A, B: T); static;
  end;

{ TSwap }

class procedure TSwap.Swap<T>(var A, B: T);
var
  C: T;
begin
  C := A;
  A := B;
  B := C;
end;
```

使い方もほぼ同じです。

```pascal
var
  a, b: Integer;
  c, d: string;
begin
  a := 3;
  b := 5;
  TSwap.Swap<Integer>(a, b);
  Writeln('a= ', a);
  Writeln('b= ', b);

  c := 'Hello,';
  d := 'world.';
  TSwap.Swap<string>(c, d);
  Writeln('c= ', c);
  Writeln('d= ', d);
end.
```

### ・パラメータ化メソッドの型推論 (XE7 以前)

パラメータ化メソッドでは**型推論** (Type Inference) が働くため、本来呼び出し時には型引数を指定しなくてもいいのですが、**Delphi XE7** 以前だとメソッドに変数パラメータ (**var**) または **out** パラメータが使われている場合に型推論が働きません。

```pascal
program GenericsTest;
{$APPTYPE CONSOLE}
uses
  SysUtils, Types;

type
  TSwap = record
  public
    class procedure Swap<T>(var A, B: T); static; // A, B は変数パラメータ
  end;

{ TSwap }

class procedure TSwap.Swap<T>(var A, B: T);
var
  C: T;
begin
  C := A;
  A := B;
  B := C;
end;

var
  a, b: Integer;
  c, d: String;
begin
  a := 3;
  b := 5;
//TSwap.Swap<Integer>(a, b);
  TSwap.Swap(a, b); // E2033 変数実パラメータと変数仮パラメータとは同一の型でなければなりません
  Writeln('a= ', a);
  Writeln('b= ', b);

  c := 'Hello,';
  d := 'world.';
//TSwap.Swap<String>(c, d);
  TSwap.Swap(c, d); // E2033 変数実パラメータと変数仮パラメータとは同一の型でなければなりません
  Writeln('c= ', c);
  Writeln('d= ', d);
end.
```

エラーが出るバージョンでは、上記 `Swap()` メソッドのパラメータから **var** を抜くとコンパイルが通るようになります。もちろん `Swap()` メソッドは正しく動作しませんが。

:::note info
Pascal 用語で "仮パラメータ" はメソッド宣言時のパラメータです (単に "パラメータ" とも)。"実パラメータ" はメソッド呼び出し時のパラメータです (単に "引数" とも)。
:::

**See also:**

 - [QC#78103: type inference not working for generic var and out parameters (Quality Central)](http://qc.embarcadero.com/wc/qcmain.aspx?d=78103)

### ・タプル
**var** / **out** パラメータの型推論の問題が長らく解決しなかったのは、ひょっとすると「汎用**タプル**を実装する予定があったから」かもしれませんね。

パラメータが 2 個と 3 個のタプルは Malcolm Groves 氏が作ったものがあります。

 - [generics.tuples (Github)](https://github.com/malcolmgroves/generics.tuples)


`Swap()` をタプルで結果を返すように書き換えたものが次のコードとなります。

```pascal
program GenericsTest;
{$APPTYPE CONSOLE}
uses
  SysUtils,
  Generics.Tuples in 'Generics.Tuples.pas';

type
  TSwap = record
  public
    class function Swap<T>(A, B: T): TTuple<T, T>; static;
  end;

{ TSwap }

class function TSwap.Swap<T>(A, B: T): TTuple<T, T>;
begin
  result := TTuple<T, T>.Create(B, A);
end;

var
  a, b: Integer;
  c, d: string;
  LTupleI : ITuple<Integer, Integer>;
  LTupleS : ITuple<string, string>;
begin
  a := 3;
  b := 5;
  LTupleI := TSwap.Swap<Integer>(a, b);
  Writeln('a= ', LTupleI.Value1);
  Writeln('b= ', LTupleI.Value2);

  c := 'Hello,';
  d := 'world.';
  LTupleS := TSwap.Swap<String>(c, d);
  Writeln('c= ', LTupleS.Value1);
  Writeln('d= ', LTupleS.Value2);
end.
```

パラメータの型が 2 つとも同じなので冗長なコードに見えますね。`Swap()` はタプルの例としては良くなかったかもしれません。単に複数の値を返したいのであれば、レコード型でいいのですから。

```pascal
program GenericsTest;
{$APPTYPE CONSOLE}
uses
  SysUtils;

type
  TResult<T> = record
    Value1: T;
    Value2: T;
  end;

  TSwap = record
  public
    class function Swap<T>(A, B: T): TResult<T>; static;
  end;

{ TSwap }

class function TSwap.Swap<T>(A, B: T): TResult<T>;
begin
  result.Value1 := B;
  result.Value2 := A;
end;

var
  a, b: Integer;
  c, d: string;
  LResultI : TResult<Integer>;
  LResultS : TResult<string>;
begin
  a := 3;
  b := 5;
  LResultI := TSwap.Swap<Integer>(a, b);
  Writeln('a= ', LResultI.Value1);
  Writeln('b= ', LResultI.Value2);

  c := 'Hello,';
  d := 'world.';
  LResultS := TSwap.Swap<String>(c, d);
  Writeln('c= ', LResultS.Value1);
  Writeln('d= ', LResultS.Value2);
end.
```

**Delphi 10.3 Rio** 以降だと、パラメータ化メソッドで型引数を省略できる上、インライン型宣言と型推論も利用可能なので、もっとスッキリ書けます。

```pascal
program GenericsTest;
{$APPTYPE CONSOLE}
uses
  SysUtils;

type
  TResult<T> = record
    Value1: T;
    Value2: T;
  end;

  TSwap = record
  public
    class function Swap<T>(A, B: T): TResult<T>; static;
  end;

{ TSwap }

class function TSwap.Swap<T>(A, B: T): TResult<T>;
begin
  result.Value1 := B;
  result.Value2 := A;
end;

begin
  var a := 3;
  var b := 5;
  var LResultI: TResult<Integer> := TSwap.Swap(a, b);
  Writeln('a= ', LResultI.Value1);
  Writeln('b= ', LResultI.Value2);

  var c := 'Hello,';
  var d := 'world.';
  var LResultS: TResult<string> := TSwap.Swap(c, d);
  Writeln('c= ', LResultS.Value1);
  Writeln('d= ', LResultS.Value2);
end.
```

今回の例の場合、そもそも 2 つの入力を 2 つで返す意味はないんですけどね。交換になってないし、このレコードを使う必要もありませんし。

```pascal
program GenericsTest;
{$APPTYPE CONSOLE}
begin
  var a := 3;
  var b := 5;
  Writeln('a= ', b);
  Writeln('b= ', a);

  var c := 'Hello,';
  var d := 'world.';
  Writeln('c= ', d);
  Writeln('d= ', c);
end.
```

つまりはこれでいいじゃない、と (w

**See also:**

 - [タプル (Wikipedia)](https://ja.wikipedia.org/wiki/%E3%82%BF%E3%83%97%E3%83%AB)

## ■ 型パラメータを使ったルーチン

Delphi では型パラメータを用いた手続きや関数 (ルーチン) を作る事は<font color="Crimson">できません</font>。似たような事をやりたいのであれば、レコードのパラメータ化 (クラス) メソッドとして実装するのが簡単だと思います。

フォームやデータモジュールがあるプロジェクトなら、それらのクラスにパラメータ化 (クラス) メソッドとして宣言すればいいので、そんなに困る制限ではないと思います。

**See also:**

 - [ジェネリック関数を作る。(Swanman's Horizon)](https://lyna.hateblo.jp/entry/20160812/1470932247)


### ・標準ジェネリック型ルーチン
先述の通り、型パラメータを使ったルーチンを記述する事はできませんが、標準ルーチンに **ジェネリック型関数** というものが存在します。

| ルーチン | 説明 |
|:---|:---|
| **function** [Default](https://docwiki.embarcadero.com/Libraries/en/System.Default)(**var** X): \<T\>; | 変数をその型のデフォルトで初期化します。 |
| **function** [GetTypeKind](https://docwiki.embarcadero.com/Libraries/en/System.GetTypeKind)(T: TypeIdentifier): Integer; | 指定された型から型の種類を返します。型の種類を知るだけなら `TypeInfo()` を使うより簡単です。|
| **function** [HasWeakRef](https://docwiki.embarcadero.com/Libraries/en/System.TypInfo.HasWeakRef)(T: TypeIdentifier): Boolean; | ARC 対応コンパイラで、型が特定のメモリサポートを必要とする弱い参照を持っているかを返します。<br>XE6 以前では [System.TypInfo.HasWeakRef](https://docwiki.embarcadero.com/Libraries/Alexandria/ja/System.TypInfo.HasWeakRef)(TypeInfo(T)) のようにする必要がありました。 |
| **function** [IsManagedType](https://docwiki.embarcadero.com/Libraries/en/System.IsManagedType)(T: TypeIdentifier): Boolean; | 文字列または動的配列の型がインメモリで管理されているかを返します。<br>XE6 以前では [System.Rtti.IsManaged](https://docwiki.embarcadero.com/Libraries/ja/System.Rtti.IsManaged)(TypeInfo(T)) のようにする必要がありました。|
| **function** [SizeOf](https://docwiki.embarcadero.com/Libraries/ja/System.SizeOf)(**var** X): Integer; | 変数または型が占めるバイト数を返します。 |
| **function** [TypeInfo](https://docwiki.embarcadero.com/Libraries/ja/System.TypeInfo)(T: TypeIdentifier): PTypeInfo; | 指定された型の RTTI 情報を返します。 |

:::note info
Delphi では Write() / Writeln() に使われている **Write パラメータ** を用いたルーチンを作る事はできませんし、同様に可変個パラメータのルーチンを作る事もできません。自己記述できないルーチンの多くは標準 Pascal や Turbo Pascal との互換性のために実装されており、極力実装しない方針となっているようです。
:::

**See also:**

 - [標準ルーチンと入出力 (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%E6%A8%99%E6%BA%96%E3%83%AB%E3%83%BC%E3%83%81%E3%83%B3%E3%81%A8%E5%85%A5%E5%87%BA%E5%8A%9B)
 - [GetTypeKind を使って擬似的に細かい型制約のジェネリクスを実現する。(Swanman's Horizon)](https://lyna.hateblo.jp/entry/20150520/1432137097)
 - [(6.1.4.) 配列定数とグローバル配列変数の初期化 (Qiita)](https://qiita.com/ht_deko/items/eedda6d38b6d0887d4ac#614-%E9%85%8D%E5%88%97%E5%AE%9A%E6%95%B0%E3%81%A8%E3%82%B0%E3%83%AD%E3%83%BC%E3%83%90%E3%83%AB%E9%85%8D%E5%88%97%E5%A4%89%E6%95%B0%E3%81%AE%E5%88%9D%E6%9C%9F%E5%8C%96)

## ■ 制約

型パラメータには制約を付ける事ができます。例えば次のジェネリックはクラス型に限定されます。

```pascal
type
  TFoo<T: class> = ...
```

次のジェネリックはコンポーネントクラスに限定されます。

```pascal
type
  TBar<T: TComponent> = ...
```

制約に使える型は次の通りです。

 - インターフェイス型 (カンマ区切りで複数指定可能)
 - クラス型
 - 予約語 **constructor**、**class**、または **record**

制約を使う主な利点は、メソッド内のエラーチェック (型チェック) を省ける事です。

**See also:**

 - [ジェネリックスの制約 (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%E3%82%B8%E3%82%A7%E3%83%8D%E3%83%AA%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AE%E5%88%B6%E7%B4%84)

## ■ 型引数の型を知る
型毎の事情を考慮しなくていいジェネリックプログラミングですが、中にはどうしても型毎の個別処理を記述しなくてはならない場合があります。例えば型毎に存在するルーチンを一つにまとめたラッパーを作るような時です。

型の情報は `TypeInfo()` 関数で取得できます。この関数は `TTypeInfo` 構造体へのポインタを返します。

```pascal
program GenericsTest;
{$APPTYPE CONSOLE}
uses
  SysUtils, TypInfo;

type
  TFoo = record
  public
    class procedure SetValue<T>(Value: T); static;
  end;

{ TFoo }

class procedure TFoo.SetValue<T>(Value: T);
var
  PTI: PTypeInfo;
begin
  PTI := TypeInfo(T);
  Writeln('Kind: ', Ord(PTI^.Kind));
  Writeln('Name: ', PTI^.Name);
end;

begin
  TFoo.SetValue<Integer>(123);   // Kind:1   Name: Integer
  TFoo.SetValue<Double>(3.14);   // Kind:4   Name: Double
  TFoo.SetValue<string>('ABC');  // Kind:18  Name: string
  TFoo.SetValue<TDateTime>(Now); // Kind:4   Name: TDateTime
end.
```

`TTypeInfo.Kind` で大まかな型の分類を、`TTypeInfo.Name` で型名を知る事ができます。

**See also:**

 - [System.TypeInfo (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.TypeInfo)
 - [System.TypInfo.TTypeInfo (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.TypInfo.TTypeInfo)
 - [System.TTypeKind (DocWiki)](https://docwiki.embarcadero.com/Libraries/Sydney/ja/System.TTypeKind)

## ■ System.Generics.Collections

`System.Generics.Collections` には、汎用的なジェネリックスが定義されています。

従来のコンテナクラスのジェネリックバージョンもあります。

| 従来のコンテナ | ジェネリックコンテナ |
|:---|:---|
|[System.Classes.TList](https://docwiki.embarcadero.com/Libraries/ja/System.Classes.TList)   | [TList\<T\>](https://docwiki.embarcadero.com/Libraries/ja/System.Generics.Collections.TList)|
|[System.Contnrs.TStack](https://docwiki.embarcadero.com/Libraries/ja/System.Contnrs.TStack) | [TStack\<T\>](https://docwiki.embarcadero.com/Libraries/ja/System.Generics.Collections.TStack)|
|[System.Contnrs.TQueue](https://docwiki.embarcadero.com/Libraries/ja/System.Contnrs.TQueue) | [TQueue\<T\>](https://docwiki.embarcadero.com/Libraries/ja/System.Generics.Collections.TQueue)|
|| [TDictionary\<TKey, TValue\>](https://docwiki.embarcadero.com/Libraries/ja/System.Generics.Collections.TDictionary)|
|[System.Contnrs.TObjectList](https://docwiki.embarcadero.com/Libraries/ja/System.Contnrs.TObjectList) | [TObjectList\<T: class\>](https://docwiki.embarcadero.com/Libraries/ja/System.Generics.Collections.TObjectList)|
|[System.Contnrs.TObjectStack](https://docwiki.embarcadero.com/Libraries/ja/System.Contnrs.TObjectStack) | [TObjectStack\<T: class\>](https://docwiki.embarcadero.com/Libraries/ja/System.Generics.Collections.TObjectStack) |
|[System.Contnrs.TObjectQueue](https://docwiki.embarcadero.com/Libraries/ja/System.Contnrs.TObjectQueue) | [TObjectQueue\<T: class\>](https://docwiki.embarcadero.com/Libraries/Sydney/ja/System.Generics.Collections.TObjectQueue) |
|| [TObjectDictionary\<TKey,TValue\>](https://docwiki.embarcadero.com/Libraries/ja/System.Generics.Collections.TObjectDictionary) |

**See also:**

 - [System.Generics.Collections (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Generics.Collections)

### ・ジェネリック版 TList
`TList` は汎用的なリストであり、従来はリストにアイテムとしてレコードを追加するのに `New()` で動的変数を作成してそのポインタを渡す必要がありました。もちろん動的変数の廃棄も自前で行わなくてはなりませんでした。

```pascal:TListTest1.dpr
program TListTest1;
{$APPTYPE CONSOLE}
uses
  System.SysUtils, System.Classes;

type
  TMyRec = record
    Code: Integer;
    Name: string;
  end;
  PMyRec = ^TMyRec;

begin
  ReportMemoryLeaksOnShutdown := True;
  var List := TList.Create; // 従来の TList
  try
    // Write
    var MyRec: PMyRec;
    for var i:=1 to 10 do
      begin
        New(MyRec);
        MyRec^.Code := i;
        MyRec^.Name := StringOfChar(Chr(Ord('A') + Pred(i)), 10);
        List.Add(MyRec);
      end;
    // Read
    for var MR in List do
      Writeln('Code=', PMyRec(MR)^.Code, ', ', 'Name=', PMyRec(MR)^.Name);
    // Release
    for var MR in List do
      Dispose(PMyRec(MR));
  finally
    List.Free;
  end;
end.
```

`Dispose()` で動的変数を廃棄しないとメモリリークします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/eb1b70d8-cf6b-1cb8-5a83-12898b66f8b9.png)
ジェネリックな `TList<T>` だと次のように書けます。

```pascal:TListTest2.dpr
program TListTest2;
{$APPTYPE CONSOLE}
uses
  System.SysUtils, System.Generics.Collections;

type
  TMyRec = record
    Code: Integer;
    Name: string;
  end;

begin
  ReportMemoryLeaksOnShutdown := True;
  var List := TList<TMyRec>.Create; // ジェネリック版 TList
  try
    // Write
    var MyRec: TMyRec;
    for var i:=1 to 10 do
      begin
        MyRec.Code := i;
        MyRec.Name := StringOfChar(Chr(Ord('A') + Pred(i)), 10);
        List.Add(MyRec);
      end;
    // Read
    for var MR in List do
      Writeln('Code=', MR.Code, ', ', 'Name=', MR.Name);
  finally
    List.Free;
  end;
end.
```

リストには値として TMyRec を追加しているので、動的変数の作成も破棄も必要ありません。

**See also:**

 - [10.1. ポインタ変数と動的変数 (対象変数) - <10> ポインタ型 (標準 Pascal 範囲内での Delphi 入門) (Qiita)](https://qiita.com/ht_deko/items/745ad4a792d277d33831#101-%E3%83%9D%E3%82%A4%E3%83%B3%E3%82%BF%E5%A4%89%E6%95%B0%E3%81%A8%E5%8B%95%E7%9A%84%E5%A4%89%E6%95%B0-%E5%AF%BE%E8%B1%A1%E5%A4%89%E6%95%B0)


## ■ Delphi における "ジェネリック" と "ジェネリックス" という用語の違い

Delphi では型パラメータを使って定義された型 (汎用体 / 総称型) の事を**ジェネリック** (Generic) と呼び、ジェネリックとパラメータ化メソッドを総称して**ジェネリックス** (Generics) と呼んでいるようです。

あと、細かい事ですが **ジェネリクス** ではなく **ジェネリックス**です (w [^3]

# おわりに
Delphi におけるジェネリックプログラミングについてでした。

:::note info
まだ追記するかもしれません。
:::

[^1]: 文法的に相性がいいので。C# も後継言語と言えなくもない？
[^2]: 最初は `型パラメータ化型 (Type parameterized type)` / `パラメータ化型 (Parameterized type)` と呼ばれていました。
[^3]: 『Object Pascal Handbook』では `ジェネリクス` なんだよなぁ...。