[Delphi] ちょっとだけ便利な Dictionary クラス

# TDictionary 
Delphi には [TDictionary](https://docwiki.embarcadero.com/Libraries/ja/System.Generics.Collections.TDictionary) というクラスがあります。
いわゆる[連想配列](https://ja.wikipedia.org/wiki/%E9%80%A3%E6%83%B3%E9%85%8D%E5%88%97)（Key-Value ペアによるデータ構造）です。

```pascal
var Dic := TDictinary<String, Integer>.Create;
try
  Dic.Add('FooKey', 100); // 'FooKey' と値 100 を登録
  Dic['FooKey'] := 42;    // 'FooKey' に 42 を代入
  var V := Dic['FooKey']; // 'FooKey' の値を取り出せる
  Writeln(V);             // 42 が表示される
finally
  Dic.Free;
end;
```

# TDictionary の面倒なところ

TDictionary は存在しないキーの値を取り出したり設定すると例外が発生します。

```pascal:例外が発生する
var Dic := TDictinary<String, Integer>.Create;
try
  Dic['BarKey'] := 42; // 存在しないキー 'BarKey' にアクセスしたため例外が発生する
finally
  Dic.Free;
end;
```

そのため [ContainsKey](https://docwiki.embarcadero.com/Libraries/ja/System.Generics.Collections.TDictionary.ContainsKey) でキーが存在するか調べたり [AddOrSetValue](https://docwiki.embarcadero.com/Libraries/ja/System.Generics.Collections.TDictionary.AddOrSetValue) メソッドを使うなどしてキーを登録しなければいけません。

```pascal:正しいコード
var Dic := TDictinary<String, Integer>.Create;
try
  Dic.AddOrSetValue('BarKey', 42);
finally
  Dic.Free;
end;
```

値の取得時と設定時ではアクセス方法が変わって気持ちが悪いわけです。

ところで [C#](https://ja.wikipedia.org/wiki/C_Sharp) の Dictionary は存在しないキーにアクセスすると自動的にキーが生成されます。

```csharp
var Dic = new Dictinary<String, Integer>();
Dic["BarKey"] = 42; // 自動的に 'BarKey' が生成される
```

Delphi でも C# みたいにアクセスしたいよお 

# つくったよ

ちょっとした改造だけで同じ事ができます。
短いので↓に全部載せました。

<details><summary>コードを表示</summary>

```pascal
(*
 * C# の Dictionary の様にキーが存在しない場合は自動的に追加される Dictionary
 *
 * PLATFORMS
 *   All
 *
 * ENVIRONMENT
 *   Delphi 11
 *
 * LICENSE
 *   Copyright (c) 2022 HOSOKAWA Jun
 *   Released under the MIT license
 *   http://opensource.org/licenses/mit-license.php
 *
 * HISTORY
 *   2022/05/12 Version 1.0.0
 *
 * Programmed by HOSOKAWA Jun (twitter: @pik)
 *)

unit PK.Utils.CSDictionary;

interface

uses
  System.Generics.Collections;

type
  TCSDictionary<K, V> = class(TDictionary<K, V>)
  private
    function GetItem(const AKey: K): V;
    procedure SetItem(const AKey: K; const AValue: V);
  public
    property Items read GetItem write SetItem; default;
  end;

implementation

{ TCSDictionary<K, V> }

function TCSDictionary<K, V>.GetItem(const AKey: K): V;
begin
  if ContainsKey(AKey) then
    Result := inherited Items[AKey]
  else
  begin
    Result := Default(V);
    Add(AKey, Result);
  end;
end;

procedure TCSDictionary<K, V>.SetItem(const AKey: K; const AValue: V);
begin
  AddOrSetValue(AKey, AValue);
end;

end.
```
</details>

## 解説

コード中の **Property Override** についてだけ解説します。
親に定義してある property を override する方法が [Property Override]((https://docwiki.embarcadero.com/RADStudio/ja/%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3#.E3.83.97.E3.83.AD.E3.83.91.E3.83.86.E3.82.A3.E3.81.AE.E3.82.AA.E3.83.BC.E3.83.90.E3.83.BC.E3.83.A9.E3.82.A4.E3.83.89.E3.81.A8.E5.86.8D.E5.AE.A3.E8.A8.80)) です。
↓こんな風に property の型を書かないで宣言した場合、親で定義されている property の可視性や reader / writer 等を変更できます（reader / writer はどちらか片方でもOK）。

```pascal
  property Items read GetItem write SetItem; default;
```

property の機能は実質的に reader / writer で定義されるので、ここを変えれば自由に機能を変更出来るというわけです。

注意したいのは property が親クラスで default 宣言されていた場合、override 側にも default 宣言が必要になるところです。
もしも default を指定しないと親クラスの default プロパティが呼ばれてしまいます。


## 使い方

```pascal
  var Dic := TCSDictionary<String, String>.Create;
  try
    // 存在しない Key 'Foo' に代入できる
    Dic['Foo'] := 'Bar';

    // 存在しない Key 'Baz' を取得できる(Default 値が返る)
    WriteLn(Dic['Baz']);
  finally
    Dic.Free;
  end;
```

便利～！

# 最後に
参照はまだしも設定はできてもいいのにな～