C# の Dictionary<TKey, TValue> の使い方

C# でキーと値のペアを管理する連想配列を使用する場合 C# では 「Dictionary」クラス を使用します。今回はこのクラスの基本的な使い方を紹介したいと思います。

宣言と初期化

使う前に以下を宣言します。

using System.Collections.Generic; // ← 必須
using Systen.Linq; // ← こっちは任意。あれば便利機能が使える。

連想配列の宣言方法は以下の通りです。

// 宣言が長くなるので左辺はvarで宣言します。
//  → Tkey はキーの型, TValue には値で使用した型を指定する
// var hogeTable = new Dictionary<TKey, TValue>();

// 内容を空で初期化
var hogeTable = new Dictionary<string, string>();

// newするときに一緒に内容を指定する
var hogeTable_2 = new Dictionary<string, string>()
{
    { "one", "abc" },
    { "tow", "def" },
    { "three", "hij" },
}
値の取り出し

Dictionary に記録されている値の取り出し方法です。

// 値の取り出し:キーが Dictionary に無いと KeyNotFoundException が発生
string value = hogeTable["aaa"];
値の追加・削除

値の追加方法は2つあります。

// 値の追加(1):既にキーがあったら上書き(例外発生しない)
hogeTable["aaa"] = "10ab";

// 値の追加(2):既にキーが存在すると ArgumentException が発生
hogeTable.Add("aaa", "10ab");

// キーと値の削除
// キーを指定してテーブル内からキーと値を削除。
// 戻り値は削除出来たらtrue、存在せず削除できない場合 false 
// 存在しないキーを指定しても例外は出ない
bool removed = hogeTable.Remove("aaa");

// 内容を全部削除
hogeTable.Clear();
Dictionary 内に値があるか確認する

テーブル内にキーと値が記録されているかどうかを調べます。

// ◆存在確認
// 戻り値が true の場合、テーブル内に存在する、false の場合存在しない
bool contains = hogeTable.ContainsKey("aaa");
Dictionary 内のデータの個数を確認する

テーブル内に格納されている要素の個数を取得するにはCountプロパティを使用します。

// データの個数を取得
int count = hogeTable.Count;
Dictionary 内のデータを列挙

Dictionary を foreach 構文で回す時は 3種類の方法があります。

まずは以下のデータを用意します。

// 3つの値をもつテーブルを作成
var theTable = new Dictionary<int, string>()
{
    { 0, "None" },
    { 1, "One" },
    { 2, "Tow" },
};

foreach で内容を全部列挙することができます。

// Keyと値を両方列挙する
foreach(KeyValuePair<int, string> item in theTable)
{
    // KeyValurPair<TKey, TValue> という型となるので .Key と .Valur を使用してキーと値にアクセスする
    Console.WriteLine($"{item,Key} {Item.Value}, "); // 0 None, 1 One, 2 Tow, と表示される
}

// キー値のみ列挙する:Keys プロパティを in の右側に置く
foreach(int item in theTable.Keys)
{
    Console.WriteLine(item); // 0, 1, 2 と表示される
}

// 値のみ列挙する:Values プロパティを in の右側に置く
foreach(string item in theTable.Values)
{
    Console.WriteLine(item); // None, One, Tow と表示される
}

その他の使い方

以下、便利な機能を紹介します。

キー・値をリストもしくは配列にする

キーや値をリストや配列に変換する方法はToArray, ToListを使用します。

// 以下宣言が必要
using System.Linq;

// キーを配列に変換
int[] keyArray = table.Keys.ToArray();
// キーをリストに変換
IList<int> keyList = table.Keys.ToList();

// 値を配列に変換
string[] valueArray = table.Values.ToArray();
// 値をリストに変換
IList<string> valueList = table.Values.ToList();

// ◆foreachで逆順に読み取る
foreach(KeyValuePair<int, string> item in table.Reverse()) // ← Reversメソッドを呼ぶと逆順になる。
{
    // Keys も Values も同じことができる
}

命名編

カタカナで書くと「ディクショナリー」ですがこの名前に引きずられないようにしましょう。

引きずられて「ディクショナリー」を名前に含めると少々かっこ悪い感じになります。
伝統的にこの手のデータ構造は テーブル(Table) とか マップ(Map) とか呼んでいるようなのでそれに従います。

// 悪い例 (1)
var hogehogeDic = new Dictionary<int, string>();
// 悪い例 (2)
var hogehogeDictionary = new Dictionary<int string>();

// 良い例
var hogehogeTable = new Dictionary<int, string>();
var hogehogeMap = new Dictionary<int, stirng>();

コメント編

ジェネリックの型はクラスを切らないで任意の値をペアにできるので便利なんですが…
3年後に見た時に TKey, TValue の意味を覚えてる可能性は低いので、それぞれどのような意味が込められているのかAPIコメントに書いておきましょう。

// APIコメントが使える場所での書き方

/// <summary>
/// hogehogeの対応関係を表すテーブル
/// TKey : キーに設定される型の意味や説明を記述する
/// TValue : 値に設定される型の意味や説明を記述する
/// </summary>
var hogehogeTable = new Dictionary<TKey, TValue>();

// ローカル変数でのコメントの書き方

// hogehogeの対応関係を表すテーブル
// TKey : キーに設定される型の意味や説明を記述する
// TValue : 値に設定される型の意味や説明を記述する
var hogehogeTable = new Dictionary<TKey, TValue>();

引数編

拡張性を保証するために、メソッドの引数や戻り値で Dictionary を使用する際は、インターフェースで受けましょう。
IDictionary という型があるためそれを用います。

// 良い例
public void Foo(IDictionary<TKey, TValue> hogehogeTable) 
{ ... }

// 悪い例
public void Foo(Dictionary<TKey, TValue> hogehogeTable)
{ ... }

悪い例の場合、「Dictionary」 が 「SortedDictionary」 や自作クラスに型が変わるとコードを変更する必要がありますが、良い例の場合、変更する必要がありません。

アンチパターン

以下避けたほうがいいパターンの紹介です。

多重辞書構造

Dictionary のジェネリックをネストするのはできるだけ避けたい所です。

短期的にはクラスを作成するより効率が良いですが、時間がたってからもう一度見直すと読み解くのに苦労することになります。

// 2重辞書
var hogehogeTable = new Dictionary<TKey, Dictionary<TKey, Dictionary>>();

// 3重辞書
var hogehogeTable = new Dictionary<TKey, Dictionary<TKey, Dictionary<TKey, TValue>>>();

自分は業務で4重辞書まで見たことありますが、最早なんだか全く分からないですね…

これなら自分でデータ構造をクラスで宣言したほうが見やすいし管理も容易です。

プロパティのアクセス修飾子の指定間違い

以下例に限らず、set を public にすると想定外のオブジェクトが設定されて処理が崩壊することが有ります。

// 外から設定できる Dictionaryオブジェクト
public class Sample
{
    public IDictionary<Tkey, TValue> HogeHogeTable { get; set; } // 外から Set できる!!!
    public Sample() { this.HogeHogeTable = new Dictionary<TKey, TValue>();
}

// で、こういう風に設定されて後でバグります。
var s = new Sample();
s.HogeHogeTable = new SortedDictionary<TKey, TValue>(); // この操作実は想定されていない!!

プロパティの set は private set にするか、削除しましょう。

まとめ

Dictionary は大変便利です。が、多用するとコードの可読性が落ちやすいのでクラスでくるんで実装するところと Dictionary を使うところを使用前によく検討しましょう。

特に、公開メソッドの引数での Dictionary の受け渡しはすぐ意味が分からなくなるのでご注意ください。Dictionary の制約(TValue は null を許容する or しないとか)とか意味(何かに関連するものでないと実は設定してはいけないとか…)は、追いにくいことが多いので受け渡し時は特に注意深く運用しましょう。

ということで、

  • 使うならスコープ内限定で短く効果的に
  • 外部公開をむやみにしない
  • ラップして制約を明示したオブジェクトの内部を検討する
  • コメントでもいいのでちゃんと文書化する

に、気を付けて使用してください。

関係する記事

Dictionaryのソート方法の詳しい紹介は以下で行っています。

takachan.hatenablog.com

自作オブジェクトをキーにする方法は以下の通りです。

takachan.hatenablog.com