Dictionary

System.Collections.Generic.Dictionaryクラスはインデックスではなくキーによってコレクション内の要素にアクセスできるコレクションで、ハッシュテーブル連想配列と呼ばれるものに相当します。

コレクション内の要素数を動的に増減できる点はListと同じですが、Dictionaryは常にキーとそれに対応する値の対(ペア)を格納します。 格納した値にアクセスする場合は、インデックスの代わりにキーを指定します。 このコレクションではキー(名前)から目的の値を引くことができるため、Dictionary(辞書)という名前が付いています。 Dictionaryでは、任意の型(厳密にはGetHashCodeメソッドが一意なハッシュ値を返すオブジェクト)をキーとして指定することができます。

DictionaryクラスはSystem.Collections.Hashtableに相当するジェネリックコレクションですが、ただ単にジェネリックなHashtableというわけではなく、Hashtableと比べるとより高度な操作が可能になっています。

基本操作

早速、Dictionaryクラスを使った基本的な操作について見ていきます。

Dictionaryの作成・要素の取得と設定

Dictionaryを使う場合には、まずキーの型と値の型を決める必要があります。 Dictionaryを作成する際には、キー・値の順で型パラメータを指定します。 例えば、キーの型がstring/String、値の型がint/IntegerのDictionaryであれば、

キーがstring、値がintのDictionary
Dictionary<string, int> dict;
キーがstring、値がintのDictionary
Dim dict As Dictionary(Of String, Integer)

となります。

Dictionaryを使う場合には、最初に格納する要素を指定する(§.コレクション初期化子)ことも、空のDictionaryを作成しておいて後から要素を追加することも出来ます。 以下の例では空のDictionaryを作成して使うことにします。

Dictionaryに格納されている値を取得(参照)するには、インデックスを指定するかわりに配列と同様の記法でキーを指定します。 そうすることにより、Dictionaryに格納されているペアの中からキーに対応する値を取得することが出来ます。 また、取得だけでなく、キーに対応する特定の要素を設定(値の上書き)をすることも出来ます。

C#ではインデクサによって、VB.NETでは既定のプロパティItemによって、配列と同様にインデックスによる取得・設定ができるようになっています。 インデクサについてはプロパティ §.インデクサを参照してください。

なお、現在Dictionary内に含まれている要素の数を取得するにはLengthプロパティではなくCountプロパティを参照します。

using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // キーの型にstring、値の型にintを使用するDictionaryを作成
    Dictionary<string, int> dict = new Dictionary<string, int>();

    dict["foo"] = 16; // キー"foo"に対応する値として16を設定
    dict["bar"] = 72; // キー"bar"に対応する値として72を設定
    dict["baz"] = 42; // キー"baz"に対応する値として42を設定

    // 格納されている要素(ペア)の数を取得
    Console.WriteLine("Count = {0}", dict.Count);

    // キー"foo"に対応する値を取得
    Console.WriteLine("foo = {0}", dict["foo"]);

    // キー"bar"に対応する値を取得
    Console.WriteLine("bar = {0}", dict["bar"]);
  }
}
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    ' キーの型にString、値の型にIntegerを使用するDictionaryを作成
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16 ' キー"foo"に対応する値として16を設定
    dict("bar") = 72 ' キー"bar"に対応する値として72を設定
    dict("baz") = 42 ' キー"baz"に対応する値として42を設定

    ' 格納されている要素(ペア)の数を取得
    Console.WriteLine("Count = {0}", dict.Count)

    ' キー"foo"に対応する値を取得
    Console.WriteLine("foo = {0}", dict("foo"))

    ' キー"bar"に対応する値を取得
    Console.WriteLine("bar = {0}", dict("bar"))
  End Sub
End Class
実行結果
Count = 3
foo = 16
bar = 72

上記サンプルで作成されるDictionaryの内容を表にすると次のようになります。

dictの内容
キー
foo 16
bar 72
baz 42

Dictionaryに対して存在しないキーを指定した場合は例外KeyNotFoundExceptionがスローされます。

using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    Dictionary<string, int> dict = new Dictionary<string, int>();

    dict["foo"] = 16;
    dict["bar"] = 72;

    // 存在するキーに対応する値を取得
    Console.WriteLine("foo = {0}", dict["foo"]);
    Console.WriteLine("bar = {0}", dict["bar"]);

    // 存在しないキー"hoge"に対応する値を取得
    Console.WriteLine("hoge = {0}", dict["hoge"]);
  }
}
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16
    dict("bar") = 72

    ' 存在するキーに対応する値を取得
    Console.WriteLine("foo = {0}", dict("foo"))
    Console.WriteLine("bar = {0}", dict("bar"))

    ' 存在しないキー"hoge"に対応する値を取得
    Console.WriteLine("hoge = {0}", dict("hoge"))
  End Sub
End Class
実行結果
foo = 16
bar = 72

ハンドルされていない例外: System.Collections.Generic.KeyNotFoundException: 指定されたキーはディレクトリ内に存在しませんでした。
   場所 System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   場所 Sample.Main()

この動作は、存在しないキーに対してnull/Nothingが返されるHashtableとは異なるので注意してください。 Dictionaryでは、インデクサで取得した値からキーが存在するかどうかを確認することはできません。 事前にキーが存在するかどうかを確かめるにはContainsKeyメソッドを使うことが出来ます。

要素の追加

既に例に挙げた通りインデクサでキーを指定することでDictionaryに要素を設定(追加)することができますが、これとは別にAddメソッドを使うことでもDictionaryに要素を追加することが出来ます。

using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // キーの型にstring、値の型にintを使用するDictionaryを作成
    Dictionary<string, int> dict = new Dictionary<string, int>();

    // Addメソッドを使って要素を追加
    dict.Add("foo", 16);
    dict.Add("bar", 72);
    dict.Add("baz", 42);

    // キーに対応する値を取得
    Console.WriteLine("foo = {0}", dict["foo"]);
    Console.WriteLine("bar = {0}", dict["bar"]);
    Console.WriteLine("baz = {0}", dict["baz"]);
  }
}
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    ' Addメソッドを使って要素を追加
    dict.Add("foo", 16)
    dict.Add("bar", 72)
    dict.Add("baz", 42)

    ' キーに対応する値を取得
    Console.WriteLine("foo = {0}", dict("foo"))
    Console.WriteLine("bar = {0}", dict("bar"))
    Console.WriteLine("baz = {0}", dict("baz"))
  End Sub
End Class
実行結果
foo = 16
bar = 72
baz = 42

一見するとインデクサを使う場合とAddメソッドを使う場合ではどちらも変わらないように見えますが、既にキーが存在する場合の動作が異なります。 インデクサでは既にキーが存在している場合は値が上書きされるのに対し、Addメソッドでは既にキーが存在している場合は例外ArgumentExceptionがスローされます

using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    Dictionary<string, int> dict = new Dictionary<string, int>();

    dict["foo"] = 16;

    Console.WriteLine("foo = {0}", dict["foo"]);

    // 既に存在するキーを指定して値を上書き
    dict["foo"] = 72;

    Console.WriteLine("foo = {0}", dict["foo"]);

    // 既に存在するキーを指定して値を追加
    dict.Add("foo", 42);

    Console.WriteLine("foo = {0}", dict["foo"]);
  }
}
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16

    Console.WriteLine("foo = {0}", dict("foo"))

    ' 既に存在するキーを指定して値を上書き
    dict("foo") = 72

    Console.WriteLine("foo = {0}", dict("foo"))

    ' 既に存在するキーを指定して値を追加
    dict.Add("foo", 42)

    Console.WriteLine("foo = {0}", dict("foo"))
  End Sub
End Class
実行結果
foo = 16
foo = 72

ハンドルされていない例外: System.ArgumentException: 同一のキーを含む項目が既に追加されています。
   場所 System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
   場所 Sample.Main()

このように、インデクサでは指定されたキーが存在しているかどうかに関わらず値が上書きされて設定されるのに対し、Addメソッドではキーが既に存在しているかを検査した上で値の追加が行われます。 事前にキーが存在するかどうかを確かめるにはContainsKeyメソッドを使います。

コレクション初期化子

Dictionaryを使用する場合、インスタンスの作成後に要素の追加を行う場合が多々あります。 コレクション初期化子を使うと、インスタンスの作成と要素の追加を同時に行うことができます。 コレクション初期化子はC# 3.0(Visual C# 2008)以降、VB 10(Visual Basic 2010)以降でサポートされます。

コレクション初期化子を使ったインスタンスの初期化
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // コレクション初期化子を使ってインスタンスの作成と要素の追加を行う
    Dictionary<string, int> dict = new Dictionary<string, int>() {
      {"foo", 16},
      {"bar", 72},
      {"baz", 42},
    };

    Console.WriteLine("foo = {0}", dict["foo"]);
    Console.WriteLine("Count = {0}", dict.Count);
  }
}
コレクション初期化子を使ったインスタンスの初期化
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    ' コレクション初期化子を使ってインスタンスの作成と要素の追加を行う
    Dim dict As New Dictionary(Of String, Integer)() From {
      {"foo", 16},
      {"bar", 72},
      {"baz", 42}
    }

    Console.WriteLine("foo = {0}", dict("foo"))
    Console.WriteLine("Count = {0}", dict.Count)
  End Sub
End Class
実行結果
foo = 16
Count = 3

コレクション初期化子では、{ {キー1, 値1}, {キー2, 値2}, ... }のように追加する要素をペア毎に中括弧で括って記述します。 RubyやPerlのハッシュで使われる{キー1 => 値1, キー2 => 値2, ...}といった形式はコレクション初期化子ではサポートされません。

要素の削除

Dictionaryから要素を削除する場合は、Removeメソッドを使います。 このメソッドでは、指定されたキーを持つ要素をDictionaryから削除し、戻り値として実際に削除に成功したかどうかがbool/Boolean型で返されます。 Addメソッドとは異なり、Removeメソッドに指定したキーを持つ要素が存在しない場合でも例外はスローされません(この場合、戻り値としてfalseが返されます)。

using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    Dictionary<string, int> dict = new Dictionary<string, int>();

    dict["foo"] = 16;
    dict["bar"] = 72;

    Console.WriteLine("Count = {0}", dict.Count);

    // 存在するキー"bar"の要素を削除
    bool ret;

    ret = dict.Remove("bar");

    Console.WriteLine("キー'bar'の要素を削除" + (ret ? "しました" : "できませんでした"));

    Console.WriteLine("Count = {0}", dict.Count);

    // 存在しないキー"baz"の要素を削除
    ret = dict.Remove("baz");

    Console.WriteLine("キー'baz'の要素を削除" + (ret ? "しました" : "できませんでした"));

    Console.WriteLine("Count = {0}", dict.Count);
  }
}
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16
    dict("bar") = 72

    Console.WriteLine("Count = {0}", dict.Count)

    ' 存在するキー"bar"の要素を削除
    Dim ret As Boolean

    ret = dict.Remove("bar")

    Console.WriteLine("キー'bar'の要素を削除" + If(ret, "しました", "できませんでした"))

    Console.WriteLine("Count = {0}", dict.Count)

    ' 存在しないキー"baz"の要素を削除
    ret = dict.Remove("baz")

    Console.WriteLine("キー'baz'の要素を削除" + If(ret, "しました", "できませんでした"))

    Console.WriteLine("Count = {0}", dict.Count)
  End Sub
End Class
実行結果
Count = 2
キー'bar'の要素を削除しました
Count = 1
キー'baz'の要素を削除できませんでした
Count = 1

Dictionary内のすべての要素を削除するにはClearメソッドを使うことが出来ます。

using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    Dictionary<string, int> dict = new Dictionary<string, int>();

    dict["foo"] = 16;
    dict["bar"] = 72;
    dict["baz"] = 42;

    Console.WriteLine("Count = {0}", dict.Count);

    // Dictionary内のすべての要素を削除
    dict.Clear();

    Console.WriteLine("Count = {0}", dict.Count);
  }
}
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16
    dict("bar") = 72
    dict("baz") = 42

    Console.WriteLine("Count = {0}", dict.Count)

    ' Dictionary内のすべての要素を削除
    dict.Clear()

    Console.WriteLine("Count = {0}", dict.Count)
  End Sub
End Class
実行結果
Count = 3
Count = 0

なお、インデクサを使って値にnull/Nothingを設定しても要素自体を削除することはできません。 この場合、指定されたキーに対応する値としてnull/Nothingが上書き設定されるだけで、要素自体は削除されません。 特にVBでは、Dictionaryの値の型が値型の場合、値としてNothingを設定すると、デフォルト値が設定される点に注意が必要です(型の種類・サイズ・精度・値域 §.型のデフォルト値値型と参照型)。

値にNothingを設定した場合の動作
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16
    dict("bar") = 72

    Console.WriteLine("Count = {0}", dict.Count)

    ' キー"bar"の要素にNothingを設定
    dict("bar") = Nothing

    ' キー"bar"に設定されている値と要素数を表示
    Console.WriteLine("bar = {0}", dict("bar"))
    Console.WriteLine("Count = {0}", dict.Count)
  End Sub
End Class
実行結果
Count = 2
bar = 0
Count = 2

値に対する変更

値に構造体を格納するようにしたDictionaryで構造体のプロパティやフィールドだけを変更しようとする場合は注意が必要です。 例えば、次のようなコードはコンパイルエラーとなります。

Dictionaryに格納されている構造体のフィールドを直接変更しようとした場合
using System;
using System.Collections.Generic;

// Dictionaryに格納する構造体
struct S {
  public int F;

  public S(int f)
  {
    F = f;
  }
}

class Sample {
  static void Main()
  {
    Dictionary<string, S> dict = new Dictionary<string, S>();

    // フィールドFに値3を設定した構造体を格納
    dict["foo"] = new S(3);

    // キー"foo"の構造体フィールドの値を変更したいが、コンパイルエラーとなる
    dict["foo"].F = 16;
    // error CS1612: 変数ではないため、'System.Collections.Generic.Dictionary<string,S>.this[string]'の戻り値を変更できません。
  }
}
Dictionaryに格納されている構造体のフィールドを直接変更しようとした場合
Imports System
Imports System.Collections.Generic

' Dictionaryに格納する構造体
Structure S
  Public F As Integer

  Public Sub New(ByVal val As Integer)
    F = val
  End Sub
End Structure

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, S)()

    ' フィールドFに値3を設定した構造体を格納
    dict("foo") = New S(3)

    ' キー"foo"の構造体フィールドの値を変更したいが、コンパイルエラーとなる
    dict("foo").F = 16
    ' error BC30068: Expression は値であるため、代入式のターゲットにすることはできません。
  End Sub
End Class

このような操作を行いたい場合は、いったんDictionaryから一時変数に構造体をコピーし、値を変更してから再度Dictionaryに格納するようにします。

Dictionaryに格納されている構造体のフィールドを変更する方法
using System;
using System.Collections.Generic;

// Dictionaryに格納する構造体
struct S {
  public int F;

  public S(int f)
  {
    F = f;
  }
}

class Sample {
  static void Main()
  {
    Dictionary<string, S> dict = new Dictionary<string, S>();

    // フィールドFに値3を設定した構造体を格納
    dict["foo"] = new S(3);

    // Dictionaryに格納した構造体フィールドの値を参照
    Console.WriteLine("dict[\"foo\"].F = {0}", dict["foo"].F);

    // フィールドの値を変更したい構造体を一時変数にコピーする
    S tmp = dict["foo"];

    // 一時変数に代入した構造体のフィールドの値を変更する
    tmp.F = 16;

    // 変更した構造体を再度Dictionaryに格納する
    dict["foo"] = tmp;

    // Dictionaryに格納した構造体フィールドの値を参照
    Console.WriteLine("dict[\"foo\"].F = {0}", dict["foo"].F);
  }
}
Dictionaryに格納されている構造体のフィールドを変更する方法
Imports System
Imports System.Collections.Generic

' Dictionaryに格納する構造体
Structure S
  Public F As Integer

  Public Sub New(ByVal val As Integer)
    F = val
  End Sub
End Structure

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, S)()

    ' フィールドFに値3を設定した構造体を格納
    dict("foo") = New S(3)

    ' Dictionaryに格納した構造体フィールドの値を参照
    Console.WriteLine("dict(""foo"").F = {0}", dict("foo").F)

    ' フィールドの値を変更したい構造体を一時変数にコピーする
    Dim tmp As S = dict("foo")

    ' 一時変数に代入した構造体のフィールドの値を変更する
    tmp.F = 16

    ' 変更した構造体を再度Dictionaryに格納する
    dict("foo") = tmp

    ' Dictionaryに格納した構造体フィールドの値を参照
    Console.WriteLine("dict(""foo"").F = {0}", dict("foo").F)
  End Sub
End Class
実行結果
dict("foo").F = 3
dict("foo").F = 16

構造体ではなくクラスのインスタンスを格納する場合はこのような操作を行う必要はありません。 この違いは型が値型か参照型かによって決まります。 詳しくは値型と参照型 §.値型のプロパティ・インデクサを参照してください。

要素の列挙

DictionaryとKeyValuePair

列挙について解説する前に、Dictionaryの内部構造とKeyValuePair構造体について触れておきます。 Dictionaryでは、要素を格納する際、キーと値の組み合わせをKeyValuePairとして格納します。 KeyValuePairはその名の通りキーとそれに対応する値のペアを表す構造体で、キーを格納するプロパティKeyと値を格納するプロパティValueの二つのメンバを持ちます。 Dictionaryの内部では、キーと値の組み合わせはKeyValuePairで管理されます。

Dictionaryに格納されるデータ
キー
"foo" 16
"bar" 72
"baz" 42
:
:
:
:
... ...
Dictionaryの内部構造のイメージ
KeyValuePair[0] Key = "foo"Value = 16
KeyValuePair[1] Key = "bar"Value = 72
KeyValuePair[2] Key = "baz"Value = 42
:
:
KeyValuePair[n] Key = ...Value = ...

なお、Dictionaryは順序を持たないコレクションであるため、実際には上図のように各KeyValuePairに対してインデックスが振られる訳ではありません。

また、DictionaryはインターフェイスICollection<KeyValuePair<TKey, TValue>>を実装しています。 つまりDictionaryはKeyValuePairのコレクションと見ることが出来ます。

要素の列挙

List等と同様にDictionaryもforeach文によって要素を列挙することができますが、Dictionaryではキー・値のペアはKeyValuePair構造体として格納されているため、列挙する場合もKeyValuePair構造体の形で列挙されます。 列挙されるKeyValuePairのKeyプロパティを参照することでキーを、Valueプロパティを参照することで値を取得することが出来ます。

Dictionaryに格納されている要素の列挙
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    Dictionary<string, int> dict = new Dictionary<string, int>();

    dict["foo"] = 16;
    dict["bar"] = 72;
    dict["baz"] = 42;

    // Dictionaryに格納されているキーと値のペアを列挙
    foreach (KeyValuePair<string, int> pair in dict) {
      Console.WriteLine("{0} => {1}", pair.Key, pair.Value);
    }
  }
}
Dictionaryに格納されている要素の列挙
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16
    dict("bar") = 72
    dict("baz") = 42

    ' Dictionaryに格納されているキーと値のペアを列挙
    For Each pair As KeyValuePair(Of String, Integer) In dict
      Console.WriteLine("{0} => {1}", pair.Key, pair.Value)
    Next
  End Sub
End Class
実行結果
foo => 16
bar => 72
baz => 42

単にDictionaryを列挙した場合はキーと値のペアで取得されることになりますが、キーだけ・値だけをそれぞれ個別に列挙することも出来ます。 Keysプロパティを参照することでDictionaryに格納されているすべてのキーを、Valuesプロパティを参照することですべての値をそれぞれ取得・列挙することが出来ます。

キーのみ・値のみの列挙
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    Dictionary<string, int> dict = new Dictionary<string, int>();

    dict["foo"] = 16;
    dict["bar"] = 72;
    dict["baz"] = 42;

    // Dictionaryに格納されているすべてのキーを列挙
    Console.WriteLine("[Keys]");

    foreach (string key in dict.Keys) {
      Console.WriteLine(key);
    }

    // Dictionaryに格納されているすべての値を列挙
    Console.WriteLine("[Values]");

    foreach (int val in dict.Values) {
      Console.WriteLine(val);
    }
  }
}
キーのみ・値のみの列挙
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16
    dict("bar") = 72
    dict("baz") = 42

    ' Dictionaryに格納されているすべてのキーを列挙
    Console.WriteLine("[Keys]")

    For Each key As String In dict.Keys
      Console.WriteLine(key)
    Next

    ' Dictionaryに格納されているすべての値を列挙
    Console.WriteLine("[Values]")

    For Each val As Integer In dict.Values
      Console.WriteLine(val)
    Next
  End Sub
End Class
実行結果
[Keys]
foo
bar
baz
[Values]
16
72
42

要素の列挙と順序

Dictionaryは順序を持たないコレクションであるため、List等とは異なり要素を列挙する場合は必ずしも追加した順で列挙されるとは限らない点に注意が必要です。 このことは、ドキュメント上では以下のように記述されています。

列挙処理のために、ディクショナリ内の各アイテムは、値とそのキーを表す KeyValuePair(Of TKey, TValue) 構造体として処理されます。 アイテムが返される順序は未定義です。
For purposes of enumeration, each item in the dictionary is treated as a KeyValuePair(Of TKey, TValue) structure representing a value and its key. The order in which the items are returned is undefined.

Dictionary(Of TKey, TValue) クラス

そのため、foreach文でDictionaryの列挙を行う場合は、列挙される順序に依存した処理を記述しないようにする必要があります。

列挙される順序が意味を持つ場合は、Dictionaryの代わりにSortedListクラス・SortedDictionaryクラスを使うことができます。 SortedList・SortedDictionaryでは、常に一定の順序で列挙されることが保証されます。 また、非ジェネリックコレクションであるOrderedDictionaryクラスでは、要素を追加した順序が維持され、列挙の際も追加された順となることが保証されます。

このほか、LINQの拡張メソッドOrderByを使うことにより、順序を定義して要素の列挙を行うこともできます。

OrderByメソッドを使って列挙順を定義する例
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  static void Main()
  {
    Dictionary<string, int> dict = new Dictionary<string, int>();

    dict["foo"] = 16;
    dict["bar"] = 72;
    dict["baz"] = 42;

    // 値の小さい順(昇順)でDictionaryに格納されている要素を列挙
    foreach (KeyValuePair<string, int> pair in dict.OrderBy(p => p.Value)) {
      Console.WriteLine("{0} => {1}", pair.Key, pair.Value);
    }
  }
}
OrderByメソッドを使って列挙順を定義する例
Imports System
Imports System.Collections.Generic
Imports System.Linq

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16
    dict("bar") = 72
    dict("baz") = 42

    ' 値の小さい順(昇順)でDictionaryに格納されている要素を列挙
    For Each pair As KeyValuePair(Of String, Integer) In dict.OrderBy(Function(p) p.Value)
      Console.WriteLine("{0} => {1}", pair.Key, pair.Value)
    Next
  End Sub
End Class
実行結果
foo => 16
baz => 42
bar => 72

OrderByメソッドについて詳しくは基本型のソートと昇順・降順でのソート §.Enumerable.OrderByで解説しています。

OrderedDictionaryクラスについてはOrderedDictionaryクラスを、またこれに関連してジェネリック版OrderedDictionaryを参照してください。

列挙中の要素の追加・削除

Dictionaryをforeach文で列挙している最中に要素の追加や削除などの変更を行おうとすると、例外InvalidOperationExceptionがスローされます。

列挙中の要素の削除
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    Dictionary<string, int> dict = new Dictionary<string, int>();

    dict["foo"] = 16;
    dict["bar"] = 72;
    dict["baz"] = 42;
    dict["hoge"] = 3;
    dict["huga"] = 42;

    foreach (KeyValuePair<string, int> pair in dict) {
      // 値が42の場合は、削除する
      if (pair.Value == 42) dict.Remove(pair.Key);
    }
  }
}
列挙中の要素の削除
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16
    dict("bar") = 72
    dict("baz") = 42
    dict("hoge") = 3
    dict("huga") = 42

    For Each pair As KeyValuePair(Of String, Integer) In dict
      ' 値が42の場合は、削除する
      If pair.Value = 42 Then dict.Remove(pair.Key)
    Next
  End Sub
End Class
実行結果
ハンドルされていない例外: System.InvalidOperationException: コレクションが変更されました。列挙操作は実行されない可能性があります。
   場所 System.Collections.Generic.Dictionary`2.Enumerator.MoveNext()
   場所 Sample.Main()

例外が発生する理由や回避する方法については列挙操作中のコレクションの変更でも解説していますが、Dictionaryクラスではインデックスを指定した列挙操作は行えないため、ここで紹介している方法は適用できません。

先の例のように条件に合う要素を検索して削除するといった場合は、列挙による要素の検索と削除を別々のforeach文で行うようにすることでInvalidOperationExceptionの発生を回避することが出来ます。

要素の検索と削除を別々に行う例
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    Dictionary<string, int> dict = new Dictionary<string, int>();

    dict["foo"] = 16;
    dict["bar"] = 72;
    dict["baz"] = 42;
    dict["hoge"] = 3;
    dict["huga"] = 42;

    // 削除対象のキーを格納するList
    List<string> removeKeys = new List<string>();

    foreach (KeyValuePair<string, int> pair in dict) {
      // 値が42の場合は、削除対象としてListにキーを追加
      if (pair.Value == 42) removeKeys.Add(pair.Key);
    }

    // 対象となるキーを持つ要素を削除
    foreach (string key in removeKeys) {
      dict.Remove(key);
    }

    // 削除後のDictionaryの内容を列挙
    foreach (KeyValuePair<string, int> pair in dict) {
      Console.WriteLine("{0} => {1}", pair.Key, pair.Value);
    }
  }
}
要素の検索と削除を別々に行う例
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16
    dict("bar") = 72
    dict("baz") = 42
    dict("hoge") = 3
    dict("huga") = 42

    ' 削除対象のキーを格納するList
    Dim removeKeys As New List(Of String)

    For Each pair As KeyValuePair(Of String, Integer) In dict
      ' 値が42の場合は、削除対象としてListにキーを追加
      If pair.Value = 42 Then removeKeys.Add(pair.Key)
    Next

    ' 対象となるキーを持つ要素を削除
    For Each key As String In removeKeys
      dict.Remove(key)
    Next

    ' 削除後のDictionaryの内容を列挙
    For Each pair As KeyValuePair(Of String, Integer) In dict
      Console.WriteLine("{0} => {1}", pair.Key, pair.Value)
    Next
  End Sub
End Class
実行結果
foo => 16
bar => 72
hoge => 3

また別の方法として、次の例のように別のDictionaryを用意し、元のDictionaryから条件に合う要素だけを別のDictionaryコピーすることで要素を削除した場合と同等のDictionaryを得るという方法もあります。

特定の要素だけを除外して新しいDictionaryを作成する例
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    Dictionary<string, int> dict = new Dictionary<string, int>();

    dict["foo"] = 16;
    dict["bar"] = 72;
    dict["baz"] = 42;
    dict["hoge"] = 3;
    dict["huga"] = 42;

    Dictionary<string, int> newdict = new Dictionary<string, int>();

    // 値が42の要素を除いて新しいDictionaryに内容をコピーする
    foreach (KeyValuePair<string, int> pair in dict) {
      // 値が42以外の場合のみ、新しいDictionaryに格納する
      if (pair.Value != 42) newdict[pair.Key] = pair.Value;
    }

    // 新しいDictionaryの内容を列挙
    foreach (KeyValuePair<string, int> pair in newdict) {
      Console.WriteLine("{0} => {1}", pair.Key, pair.Value);
    }
  }
}
特定の要素だけを除外して新しいDictionaryを作成する例
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16
    dict("bar") = 72
    dict("baz") = 42
    dict("hoge") = 3;
    dict("huga") = 42;

    Dim newdict As New Dictionary(Of String, Integer)()

    ' 値が42の要素を除いて新しいDictionaryに内容をコピーする
    For Each pair As KeyValuePair(Of String, Integer) In dict
      If pair.Value <> 42 Then newdict(pair.Key) = pair.Value
    Next

    ' 新しいDictionaryの内容を列挙
    For Each pair As KeyValuePair(Of String, Integer) In newdict
      Console.WriteLine("{0} => {1}", pair.Key, pair.Value)
    Next
  End Sub
End Class
実行結果
foo => 16
bar => 72
hoge => 3

特定要素を除外した列挙が行えればよいのであれば、LINQの拡張メソッドWhereを使って次のようにすることもできます。

Whereメソッドを使用して特定の要素だけを除外して要素を列挙する例
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  static void Main()
  {
    Dictionary<string, int> dict = new Dictionary<string, int>();

    dict["foo"] = 16;
    dict["bar"] = 72;
    dict["baz"] = 42;
    dict["hoge"] = 3;
    dict["huga"] = 42;

    // 値が42以外の要素のみを列挙
    foreach (KeyValuePair<string, int> pair in dict.Where(p => p.Value != 42)) {
      Console.WriteLine("{0} => {1}", pair.Key, pair.Value);
    }
  }
}
Whereメソッドを使用して特定の要素だけを除外して要素を列挙する例
Imports System
Imports System.Collections.Generic
Imports System.Linq

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16
    dict("bar") = 72
    dict("baz") = 42
    dict("hoge") = 3
    dict("huga") = 42

    ' 値が42以外の要素のみを列挙
    For Each pair As KeyValuePair(Of String, Integer) In dict.Where(Function(p) p.Value <> 42)
      Console.WriteLine("{0} => {1}", pair.Key, pair.Value)
    Next
  End Sub
End Class
実行結果
foo => 16
bar => 72
hoge => 3

要素の並べ替え (ソート)

Dictionaryは順序を持たないことから、Dictionaryをソートされた状態にすることはできません。 従ってSort()のようなメソッドも用意されていません。 Dictionary内の各ペアをソートされた状態にしたい場合は、次の例のように一旦Listに変換し、List.Sortメソッドを使って並べ替えを行う、といった方法をとる必要があります。

DictionaryをListに変換してソートする例
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    Dictionary<string, int> dict = new Dictionary<string, int>();

    dict["foo"] = 16;
    dict["bar"] = 72;
    dict["baz"] = 42;

    // DictionaryをList<KeyValuePair>に変換
    List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>(dict);

    // List.Sortメソッドを使い、キーによってペアをソートする
    Console.WriteLine("[sort by key]");

    list.Sort((x, y) => string.Compare(x.Key, y.Key));

    foreach (KeyValuePair<string, int> pair in list) {
      Console.WriteLine("{0} => {1}", pair.Key, pair.Value);
    }

    // List.Sortメソッドを使い、値によってペアをソートする
    Console.WriteLine("[sort by value]");

    list.Sort((x, y) => x.Value - y.Value);

    foreach (KeyValuePair<string, int> pair in list) {
      Console.WriteLine("{0} => {1}", pair.Key, pair.Value);
    }
  }
}
DictionaryをListに変換してソートする例
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16
    dict("bar") = 72
    dict("baz") = 42

    ' DictionaryをList(Of KeyValuePair)に変換
    Dim list As New List(Of KeyValuePair(Of String, Integer))(dict)

    ' List.Sortメソッドを使い、キーによってペアをソートする
    Console.WriteLine("[sort by key]")

    list.Sort(Function(x, y) String.Compare(x.Key, y.Key))

    For Each pair As KeyValuePair(Of String, Integer) In list
      Console.WriteLine("{0} => {1}", pair.Key, pair.Value)
    Next

    ' List.Sortメソッドを使い、値によってペアをソートする
    Console.WriteLine("[sort by value]")

    list.Sort(Function(x, y) x.Value - y.Value)

    For Each pair As KeyValuePair(Of String, Integer) In list
      Console.WriteLine("{0} => {1}", pair.Key, pair.Value)
    Next
  End Sub
End Class
実行結果
[sort by key]
bar => 72
baz => 42
foo => 16
[sort by value]
foo => 16
baz => 42
bar => 72

このほか、Dictionaryをソートする方法については基本型のソートと昇順・降順でのソート §.Dictionaryのソートで詳しく解説しています。

要素を常に並べ替えた状態にしておきたい場合や、格納される要素の順序が重要になる場合は、Dictionaryの代わりにSortedListやSortedDictionaryを使うことも出来ます。

要素の検索

キー・値の有無チェック (ContainsKey, ContainsValue)

Dictionaryに指定したキーが存在するかどうかを調べるにはContainsKeyメソッド、値が存在するかどうかを調べるにはContainsValueメソッドを使います。

要素の検索
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    Dictionary<string, int> dict = new Dictionary<string, int>();

    dict["foo"] = 16;
    dict["bar"] = 72;
    dict["baz"] = 42;

    // キー"foo"を持つ要素があるかどうか
    Console.WriteLine("ContainsKey(foo) = {0}", dict.ContainsKey("foo"));

    // キー"hoge"を持つ要素があるかどうか
    Console.WriteLine("ContainsKey(hoge) = {0}", dict.ContainsKey("hoge"));

    // 値16を持つ要素があるかどうか
    Console.WriteLine("ContainsValue(16) = {0}", dict.ContainsValue(16));

    // 値9を持つ要素があるかどうか
    Console.WriteLine("ContainsValue(9) = {0}", dict.ContainsValue(9));
  }
}
要素の検索
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16
    dict("bar") = 72
    dict("baz") = 42

    ' キー"foo"を持つ要素があるかどうか
    Console.WriteLine("ContainsKey(foo) = {0}", dict.ContainsKey("foo"))

    ' キー"hoge"を持つ要素があるかどうか
    Console.WriteLine("ContainsKey(hoge) = {0}", dict.ContainsKey("hoge"))

    ' 値16を持つ要素があるかどうか
    Console.WriteLine("ContainsValue(16) = {0}", dict.ContainsValue(16))

    ' 値9を持つ要素があるかどうか
    Console.WriteLine("ContainsValue(9) = {0}", dict.ContainsValue(9))
  End Sub
End Class
実行結果
ContainsKey(foo) = True
ContainsKey(hoge) = False
ContainsValue(16) = True
ContainsValue(9) = False

キーの有無チェックと値の取得 (TryGetValue)

Dictionaryでは、キーが存在しない場合は例外KeyNotFoundExceptionがスローされるため、事前にキーがあるかどうか調べるなどする必要があります。 しかし、TryGetValueメソッドを使うとキーの有無チェックと値の取得を同時に行えるため、キーの有無チェックやKeyNotFoundExceptionのキャッチをする必要が無くなります。

このメソッドは、ContainsKeyメソッドと値の取得を組み合わせた動作をするメソッドと言えます。 キーの有無はTryGetValueメソッドの戻り値で取得でき、該当するキーが存在する場合はキーに対応する値がoutパラメータに格納されます。

using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    Dictionary<string, int> dict = new Dictionary<string, int>();

    dict["foo"] = 16;
    dict["bar"] = 72;

    // Dictionaryからキー"foo"に対応する値を取得
    int val;

    if (dict.TryGetValue("foo", out val)) {
      // 取得できたら表示
      Console.WriteLine("foo => {0}", val);
    }

    // Dictionaryからキー"hoge"に対応する値を取得
    if (dict.TryGetValue("hoge", out val)) {
      // 取得できたら表示
      Console.WriteLine("hoge => {0}", val);
    }
    else {
      // 存在するキーが無い場合
      Console.WriteLine("key 'hoge' not found");
    }
  }
}
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16
    dict("bar") = 72

    ' Dictionaryからキー"foo"に対応する値を取得
    Dim val As Integer

    If dict.TryGetValue("foo", val) Then
      ' 取得できたら表示
      Console.WriteLine("foo => {0}", val)
    End If

    ' Dictionaryからキー"hoge"に対応する値を取得
    If dict.TryGetValue("hoge", val) Then
      ' 取得できたら表示
      Console.WriteLine("hoge => {0}", val)
    Else
      ' 存在するキーが無い場合
      Console.WriteLine("key 'hoge' not found")
    End If
  End Sub
End Class
実行結果
foo => 16
key 'hoge' not found

値からキーの逆引き検索

Dictionaryには値から対応するキーを逆引きするメソッドは用意されていません。 そのため、次の例のようにDictionaryの全要素を列挙して該当するKeyValuePairを検索するよう独自に実装する必要があります。

値から対応するキーを逆引きする例
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    Dictionary<string, int> dict = new Dictionary<string, int>();

    dict["foo"] = 16;
    dict["bar"] = 72;
    dict["baz"] = 42;

    // 値に42を持つ要素を検索する
    int val = 42;
    bool found = false;
    KeyValuePair<string, int> match = new KeyValuePair<string, int>();

    foreach (KeyValuePair<string, int> pair in dict) {
      if (pair.Value == val) {
        // 該当する値を持つ要素が見つかった場合
        found = true;
        match = pair;
        break;
      }
    }

    if (found) {
      Console.WriteLine("値{0}を持つ要素のキーは'{1}'です", match.Value, match.Key);
    }
    else {
      Console.WriteLine("値{0}を持つ要素は見つかりませんでした", val);
    }
  }
}
値から対応するキーを逆引きする例
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16
    dict("bar") = 72
    dict("baz") = 42

    ' 値に42を持つ要素を検索する
    Dim val As Integer = 42
    Dim found As Boolean = False
    Dim match As New KeyValuePair(Of String, Integer)()

    For Each pair As KeyValuePair(Of String, Integer) In dict
      If pair.Value = val Then
        ' 該当する値を持つ要素が見つかった場合
        found = True
        match = pair
        Exit For
      End If
    Next

    If found Then
      Console.WriteLine("値{0}を持つ要素のキーは'{1}'です", match.Value, match.Key)
    Else
      Console.WriteLine("値{0}を持つ要素は見つかりませんでした", val)
    End If
  End Sub
End Class
実行結果
値42を持つ要素のキーは'baz'です

LINQの拡張メソッドFirstを使って逆引きする場合は次のように記述できます。

Firstメソッドを使って値から対応するキーを逆引きする例
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  static void Main()
  {
    Dictionary<string, int> dict = new Dictionary<string, int>();

    dict["foo"] = 16;
    dict["bar"] = 72;
    dict["baz"] = 42;

    // 値に42を持つ要素を検索する
    int val = 42;

    try {
      KeyValuePair<string, int> match = dict.First(pair => pair.Value == val);

      Console.WriteLine("値{0}を持つ要素のキーは'{1}'です", match.Value, match.Key);
    }
    catch (InvalidOperationException) {
      Console.WriteLine("値{0}を持つ要素は見つかりませんでした", val);
    }
  }
}
Firstメソッドを使って値から対応するキーを逆引きする例
Imports System
Imports System.Collections.Generic
Imports System.Linq

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16
    dict("bar") = 72
    dict("baz") = 42

    ' 値に42を持つ要素を検索する
    Dim val As Integer = 42

    Try
      Dim match As KeyValuePair(Of String, Integer) = dict.First(Function(pair) pair.Value = val)

      Console.WriteLine("値{0}を持つ要素のキーは'{1}'です", match.Value, match.Key)
    Catch ex As InvalidOperationException
      Console.WriteLine("値{0}を持つ要素は見つかりませんでした", val)
    End Try
  End Sub
End Class
実行結果
値42を持つ要素のキーは'baz'です

最初の要素・最後の要素

§.要素の列挙と順序でも述べたようにDictionaryに格納される要素には順序の概念が無いため、最初の要素・最後の要素は定義することができず、そういった要素を取得するプロパティ等も提供されません。

LINQの拡張メソッドOrderByで並べ替えを行い、その中から最初の要素・最後の要素を取得したい場合には同じく拡張メソッドのFirstメソッドLastメソッドを使うことができます。

Firstメソッド・Lastメソッドを使って最初・最後に列挙される要素を取得する例
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  static void Main()
  {
    Dictionary<string, int> dict = new Dictionary<string, int>();

    dict["foo"] = 16;
    dict["bar"] = 72;
    dict["baz"] = 42;

    // キーで並べ替えた場合に最初に位置する要素を取得する
    KeyValuePair<string, int> first = dict.OrderBy(pair => pair.Key).First();

    Console.WriteLine("first of order by key: {0} => {1}", first.Key, first.Value);

    // キーで並べ替えた状態で最後に列挙される要素を取得する
    KeyValuePair<string, int> last = dict.OrderBy(pair => pair.Key).Last();

    Console.WriteLine("last of order by key: {0} => {1}", last.Key, last.Value);

    // 値で並べ替えた場合に最初に位置する要素を取得する
    first = dict.OrderBy(pair => pair.Value).First();

    Console.WriteLine("first of order by value: {0} => {1}", first.Key, first.Value);

    // 値で並べ替えた場合に最後に位置する要素を取得する
    last = dict.OrderBy(pair => pair.Value).Last();

    Console.WriteLine("last of order by value: {0} => {1}", last.Key, last.Value);
  }
}
Firstメソッド・Lastメソッドを使って最初・最後に列挙される要素を取得する例
Imports System
Imports System.Collections.Generic
Imports System.Linq

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16
    dict("bar") = 72
    dict("baz") = 42

    ' キーで並べ替えた場合に最初に位置する要素を取得する
    Dim first As KeyValuePair(Of String, Integer) = dict.OrderBy(Function(pair) pair.Key).First()

    Console.WriteLine("first of order by key: {0} => {1}", first.Key, first.Value)

    ' キーで並べ替えた状態で最後に列挙される要素を取得する
    Dim last As KeyValuePair(Of String, Integer) = dict.OrderBy(Function(pair)  pair.Key).Last()

    Console.WriteLine("last of order by key: {0} => {1}", last.Key, last.Value)

    ' 値で並べ替えた場合に最初に位置する要素を取得する
    first = dict.OrderBy(Function(pair) pair.Value).First()

    Console.WriteLine("first of order by value: {0} => {1}", first.Key, first.Value)

    ' 値で並べ替えた場合に最後に位置する要素を取得する
    last = dict.OrderBy(Function(pair) pair.Value).Last()

    Console.WriteLine("last of order by value: {0} => {1}", last.Key, last.Value)
  End Sub
End Class
実行結果
first of order by key: bar => 72
last of order by key: foo => 16
first of order by value: foo => 16
last of order by value: bar => 72

Dictionaryとキー

キー比較のカスタマイズ(大文字小文字の違いの無視)

Hashtableと同様、コンストラクタで適切なIEqualityComparer<T>インターフェイスを指定することで、Dictionaryのキー比較時の動作をカスタマイズ出来ます。 例えば、文字列をキーとした場合に大文字小文字を無視するようにするといったことが出来ます。 以下は、StringComparerクラスを使って、大文字小文字を無視するDictionaryを作成する例です。

using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 大文字小文字の違いを無視しないDictionaryを作成
    // (キーの比較にStringComparer.CurrentCultureを使用)
    Dictionary<string, int> caseSensitiveDictionary = new Dictionary<string, int>(StringComparer.CurrentCulture);

    caseSensitiveDictionary["foo"] = 1;
    caseSensitiveDictionary["bar"] = 2;
    caseSensitiveDictionary["FOO"] = 3;
    caseSensitiveDictionary["BAR"] = 4;

    // 大文字小文字の違いを無視するDictionaryを作成
    // (キーの比較にStringComparer.CurrentCultureIgnoreCaseを使用)
    Dictionary<string, int> caseInsensitiveDictionary = new Dictionary<string, int>(StringComparer.CurrentCultureIgnoreCase);

    caseInsensitiveDictionary["foo"] = 1;
    caseInsensitiveDictionary["bar"] = 2;
    caseInsensitiveDictionary["FOO"] = 3;
    caseInsensitiveDictionary["BAR"] = 4;

    Console.WriteLine("caseSensitiveDictionary");

    foreach (KeyValuePair<string, int> entry in caseSensitiveDictionary) {
      Console.WriteLine("{0} => {1}", entry.Key, entry.Value);
    }

    Console.WriteLine("caseInsensitiveDictionary");

    foreach (KeyValuePair<string, int> entry in caseInsensitiveDictionary) {
      Console.WriteLine("{0} => {1}", entry.Key, entry.Value);
    }
  }
}
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    ' 大文字小文字の違いを無視しないDictionaryを作成
    ' (キーの比較にStringComparer.CurrentCultureを使用)
    Dim caseSensitiveDictionary As New Dictionary(Of String, Integer)(StringComparer.CurrentCulture)

    caseSensitiveDictionary("foo") = 1
    caseSensitiveDictionary("bar") = 2
    caseSensitiveDictionary("FOO") = 3
    caseSensitiveDictionary("BAR") = 4

    ' 大文字小文字の違いを無視するDictionaryを作成
    ' (キーの比較にStringComparer.CurrentCultureIgnoreCaseを使用)
    Dim caseInsensitiveDictionary As New Dictionary(Of String, Integer)(StringComparer.CurrentCultureIgnoreCase)

    caseInsensitiveDictionary("foo") = 1
    caseInsensitiveDictionary("bar") = 2
    caseInsensitiveDictionary("FOO") = 3
    caseInsensitiveDictionary("BAR") = 4

    Console.WriteLine("caseSensitiveDictionary")

    For Each entry As KeyValuePair(Of String, Integer) In caseSensitiveDictionary
      Console.WriteLine("{0} => {1}", entry.Key, entry.Value)
    Next

    Console.WriteLine("caseInsensitiveDictionary")

    For Each entry As KeyValuePair(Of String, Integer) In caseInsensitiveDictionary
      Console.WriteLine("{0} => {1}", entry.Key, entry.Value)
    Next
  End Sub
End Class
実行結果
caseSensitiveDictionary
foo => 1
bar => 2
FOO => 3
BAR => 4
caseInsensitiveDictionary
foo => 3
bar => 4

キーと複合型

Dictionaryクラスでは、キーとなる型がEqualsメソッドGetHashCodeメソッドをオーバーライドしていて、かつ適切な値を返すように実装されていないと正しく動作しません。 キーに独自に定義した型を指定してDictionaryを使用したい場合は、これらのメソッドをオーバーライドして適切に実装するか、DictionaryのコンストラクタにIEqualityComparer<T>インターフェイスを指定する必要があります。

IEqualityComparer<T>インターフェイスの実装方法や、独自に定義した型をキーにする例については等価性の定義と比較で解説しています。

Dictionaryとインデックス

Dictionaryでは格納される要素はキーによってのみ管理されます。 また順序を持たないコレクションであるためインデックスという概念を持たず、インデックスを指定したキー・値の参照・設定などの操作も提供されません。 これに従って、for文による列挙も行えません。 当然、IndexOfKey・IndexOfValueといったキー・値のインデックスを取得するような操作も提供されません。

インデックスを指定した操作

インデックスを使った操作を行いたい場合は、次の例のように一旦DictionaryからListに変換する、といった方法を取る必要があります。

DictionaryからListに変換して列挙する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    Dictionary<string, int> dict = new Dictionary<string, int>();

    dict["foo"] = 16;
    dict["bar"] = 72;
    dict["baz"] = 42;

    // Dictionaryと同じ内容を含むListを作成
    List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>(dict);

    // for文を使って作成したListを列挙
    for (int i = 0; i < list.Count; i++) {
      Console.WriteLine("{0} : {1} => {2}", i, list[i].Key, list[i].Value);
    }
  }
}
DictionaryからListに変換して列挙する
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16
    dict("bar") = 72
    dict("baz") = 42

    ' Dictionaryと同じ内容を含むListを作成
    Dim list As New List(Of KeyValuePair(Of String, Integer))(dict)

    ' Forステートメントを使って作成したListを列挙
    For i As Integer = 0 To list.Count - 1
      Console.WriteLine("{0} : {1} => {2}", i, list(i).Key, list(i).Value)
    Next
  End Sub
End Class
実行結果
0 : foo => 16
1 : bar => 72
2 : baz => 42

キーもしくは値を配列にコピーしてから列挙したい場合は、次の例のようにKeysプロパティ・Valuesプロパティを参照してCopyToメソッドによって配列にコピーした上で列挙するという方法がとれます。

Dictionaryのすべてのキーと値を配列にコピーして列挙する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    Dictionary<string, int> dict = new Dictionary<string, int>();

    dict["foo"] = 16;
    dict["bar"] = 72;
    dict["baz"] = 42;

    // キーと値を格納するための配列を作成
    string[] keys = new string[dict.Count];
    int[] values = new int[dict.Count];

    // キーと値を配列にコピー
    dict.Keys.CopyTo(keys, 0);
    dict.Values.CopyTo(values, 0);

    // for文を使ってコピーしたキーと値の配列を列挙
    for (int i = 0; i < keys.Length; i++) {
      Console.WriteLine("{0} : {1} => {2}", i, keys[i], values[i]);
    }
  }
}
Dictionaryのすべてのキーと値を配列にコピーして列挙する
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16
    dict("bar") = 72
    dict("baz") = 42

    ' キーと値を格納するための配列を作成
    Dim keys(dict.Count - 1) As String
    Dim values(dict.Count - 1) As Integer

    ' キーと値を配列にコピー
    dict.Keys.CopyTo(keys, 0)
    dict.Values.CopyTo(values, 0)

    ' Forステートメントを使ってコピーしたキーと値の配列を列挙
    For i As Integer = 0 To keys.Length - 1
      Console.WriteLine("{0} : {1} => {2}", i, keys(i), values(i))
    Next
  End Sub
End Class
実行結果
0 : foo => 16
1 : bar => 72
2 : baz => 42

キーの型をintなどの整数型にすることでインデックスを持たせたDictionaryのように扱うことはできますが、§.要素の列挙と順序でも述べたようにDictionaryに格納された要素をforeach文で列挙する際の順序は不定である点、インデックス(キー)が不連続な値となりうる点などから、あまり推奨できる方法ではありません。

Dictionaryのキーをインデックスとして扱う例
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // キーをintにしてインデックスとして扱う
    Dictionary<int, string> dict = new Dictionary<int, string>();

    dict[0] = "foo";
    dict[1] = "bar";
    dict[3] = "baz";

    for (int i = 0; i < dict.Count; i++) {
      // キー2の要素は存在しないため、途中で例外が発生する
      Console.WriteLine("{0} => {1}", i, dict[i]);
    }
  }
}
Dictionaryのキーをインデックスとして扱う例
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    ' キーをIntegerにしてインデックスとして扱う
    Dim dict As New Dictionary(Of Integer, String)()

    dict(0) = "foo"
    dict(1) = "bar"
    dict(3) = "baz"

    For i As Integer = 0 To dict.Count - 1
      ' キー2の要素は存在しないため、途中で例外が発生する
      Console.WriteLine("{0} => {1}", i, dict(i))
    Next
  End Sub
End Class
実行結果
0 => foo
1 => bar

ハンドルされていない例外: System.Collections.Generic.KeyNotFoundException: 指定されたキーはディレクトリ内に存在しませんでした。
   場所 System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   場所 Sample.Main()

SortedList

SortedListを使うと、キーと値のペアにインデックスを持たせることができます。 ただし、SortedListではインデックスを指定して要素を格納することはできません。 インデックスは常にキーに従って要素を並べ替えた時の順序で自動的に割り振られます。

DictionaryのかわりにSortedListを使う例
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    SortedList<string, int> list = new SortedList<string, int>();

    list["foo"] = 16;
    list["bar"] = 72;
    list["baz"] = 42;

    Console.WriteLine("IndexOfKey(foo) = {0}", list.IndexOfKey("foo"));
    Console.WriteLine("IndexOfValue(42) = {0}", list.IndexOfValue(42));

    for (int i = 0; i < list.Count; i++) {
      Console.WriteLine("{0}: {1} => {2}", i, list.Keys[i], list.Values[i]);
    }
  }
}
DictionaryのかわりにSortedListを使う例
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim list As New SortedList(Of String, Integer)()

    list("foo") = 16
    list("bar") = 72
    list("baz") = 42

    Console.WriteLine("IndexOfKey(foo) = {0}", list.IndexOfKey("foo"))
    Console.WriteLine("IndexOfValue(42) = {0}", list.IndexOfValue(42))

    For i As Integer = 0 To list.Count - 1
      Console.WriteLine("{0}: {1} => {2}", i, list.Keys(i), list.Values(i))
    Next
  End Sub
End Class
実行結果
IndexOfKey(foo) = 2
IndexOfValue(42) = 1
0: bar => 72
1: baz => 42
2: foo => 16

OrderedDictionary

System.Collections.Specialized名前空間のOrderedDictionaryクラスでは、要素が格納された順にインデックスが割り振られ、列挙する際も常に格納された順で列挙されます。 ただし、OrderedDictionaryは非ジェネリックなコレクションです。 OrderedDictionaryに相当するジェネリックコレクションは用意されていません。

DictionaryのかわりにOrderedDictionaryを使う例
using System;
using System.Collections;
using System.Collections.Specialized;

class Sample {
  static void Main()
  {
    OrderedDictionary dict = new OrderedDictionary();

    dict["foo"] = 16;
    dict["bar"] = 72;
    dict["baz"] = 42;

    // OrderedDictionaryに格納された要素を列挙
    foreach (DictionaryEntry entry in dict) {
      Console.WriteLine("{0} => {1}", entry.Key, entry.Value);
    }

    // OrderedDictionaryに格納されている値をインデックス順で列挙
    for (int i = 0; i < dict.Count; i++) {
      Console.WriteLine("{0}: {1}", i, dict[i]);
    }
  }
}
DictionaryのかわりにOrderedDictionaryを使う例
Imports System
Imports System.Collections
Imports System.Collections.Specialized

Class Sample
  Shared Sub Main()
    Dim dict As New OrderedDictionary()

    dict("foo") = 16
    dict("bar") = 72
    dict("baz") = 42

    ' OrderedDictionaryに格納された要素を列挙
    For Each entry As DictionaryEntry In dict
      Console.WriteLine("{0} => {1}", entry.Key, entry.Value)
    Next

    ' OrderedDictionaryに格納されている値をインデックス順で列挙
    For i As Integer = 0 To dict.Count - 1
      Console.WriteLine("{0}: {1}", i, dict(i))
    Next
  End Sub
End Class
実行結果
foo => 16
bar => 72
baz => 42
0: 16
1: 72
2: 42

OrderedDictionaryクラスについてはOrderedDictionaryクラスを、またこれに関連してジェネリック版OrderedDictionaryを参照してください。

入れ子

Listや他のジェネリックコレクション同様、Dictionaryもキー・値ともに任意の型に型付けすることができるため、キー・値に他のジェネリックコレクション型を格納するようにすることも出来ます。 次の例では、Dictionaryクラスを使って文字列の辞書を処理するために入れ子になったDictionary<char, List<string>>を作成し、要素の追加・削除・列挙などの操作を行っています。

using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 値にList<string>を格納するDictionaryを作成
    Dictionary<char, List<string>> dictionary = new Dictionary<char, List<string>>();

    dictionary.Add('a', new List<string>() {"apple", "action", "after"}); // 'a'で始まる単語のListを追加
    dictionary.Add('b', new List<string>() {"big", "best", "bridge"});    // 'b'で始まる単語のListを追加
    dictionary.Add('c', new List<string>() {"cheese", "cat", "connect"}); // 'c'で始まる単語のListを追加

    dictionary['a'].Add("add");         // 'a'で始まる単語のListにアイテムを追加
    dictionary['b'].Add("book");        // 'b'で始まる単語のListにアイテムを追加
    dictionary['c'].Remove("connect");  // 'c'で始まる単語のListからアイテムを削除

    // すべての単語のListをソートする
    foreach (List<string> words in dictionary.Values) {
      words.Sort();
    }

    // 辞書の内容を表示
    foreach (KeyValuePair<char, List<string>> pair in dictionary) {
      Console.Write("'{0}': ", pair.Key);

      // Listの内容を表示
      List<string> words = pair.Value;

      foreach (string word in words) {
        Console.Write("{0} ", word);
      }

      Console.WriteLine();
    }
  }
}
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    ' 値にList(Of String)を格納するDictionaryを作成
    Dim dictionary As New Dictionary(Of Char, List(Of String))()

    dictionary.Add("a"c, New List(Of String)(New String() {"apple", "action", "after"}))  ' 'a'で始まる単語のListを追加
    dictionary.Add("b"c, New List(Of String)(New String() {"big", "best", "bridge"}))     ' 'b'で始まる単語のListを追加
    dictionary.Add("c"c, New List(Of String)(New String() {"cheese", "cat", "connect"}))  ' 'c'で始まる単語のListを追加

    dictionary("a"c).Add("add")         ' 'a'で始まる単語のListにアイテムを追加
    dictionary("a"c).Add("book")        ' 'b'で始まる単語のListにアイテムを追加
    dictionary("a"c).Remove("connect")  ' 'c'で始まる単語のListからアイテムを削除

    ' すべての単語のListをソートする
    For Each words As List(Of String) In dictionary.Values
      words.Sort()
    Next

    ' 辞書の内容を表示
    For Each pair As KeyValuePair(Of Char, List(Of String)) In dictionary
      Console.Write("'{0}': ", pair.Key)

      ' Listの内容を表示
      Dim words As List(Of String) = pair.Value

      For Each word As String In words
        Console.Write("{0} ", word)
      Next

      Console.WriteLine()
    Next
  End Sub
End Class
実行結果
'a': action add after apple 
'b': best big book bridge 
'c': cat cheese 

同様に、DictionaryのDictionaryを作成することも可能です。

using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // 値にDictionary<string, string[]>を格納するDictionaryを作成
    Dictionary<string, Dictionary<string, string[]>> mimeTypes = new Dictionary<string, Dictionary<string, string[]>>();

    // text/*に該当する拡張子
    mimeTypes["text"] = new Dictionary<string, string[]>();
    mimeTypes["text"]["plain"] = new string[] {".txt"};
    mimeTypes["text"]["html"] = new string[] {".htm", ".html"};

    // image/*に該当する拡張子
    mimeTypes["image"] = new Dictionary<string, string[]>();
    mimeTypes["image"]["gif"] = new string[] {".gif"};
    mimeTypes["image"]["png"] = new string[] {".png"};
    mimeTypes["image"]["jpeg"] = new string[] {".jpg", ".jpeg", ".jpe"};

    // 内容を表示
    foreach (KeyValuePair<string, Dictionary<string, string[]>> mimeType in mimeTypes) {
      Console.WriteLine("{0}/*", mimeType.Key);

      foreach (KeyValuePair<string, string[]> mimeSubType in mimeType.Value) {
        Console.Write("  {0}/{1} : ", mimeType.Key, mimeSubType.Key);

        foreach (string extension in mimeSubType.Value) {
          Console.Write("{0} ", extension);
        }

        Console.WriteLine();
      }
    }
  }
}
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    ' 値にDictionary(Of String, String())を格納するDictionaryを作成
    Dim mimeTypes As New Dictionary(Of String, Dictionary(Of String, String()))

    ' text/*に該当する拡張子
    mimeTypes("text") = New Dictionary(Of String, String())()
    mimeTypes("text")("plain") = New String() {".txt"}
    mimeTypes("text")("html") = New String() {".htm", ".html"}

    ' image/*に該当する拡張子
    mimeTypes("image") = New Dictionary(Of String, String())()
    mimeTypes("image")("gif") = New String() {".gif"}
    mimeTypes("image")("png") = New String() {".png"}
    mimeTypes("image")("jpeg") = New String() {".jpg", ".jpeg", ".jpe"}

    ' 内容を表示
    For Each mimeType As KeyValuePair(Of String, Dictionary(Of String, String())) In mimeTypes
      Console.WriteLine("{0}/*", mimeType.Key)

      For Each mimeSubType As KeyValuePair(Of String, String()) In mimeType.Value
        Console.Write("  {0}/{1} : ", mimeType.Key, mimeSubType.Key)

        For Each extension As String In mimeSubType.Value
          Console.Write("{0} ", extension)
        Next

        Console.WriteLine()
      Next
    Next
  End Sub
End Class
実行結果
text/*
  text/plain : .txt 
  text/html : .htm .html 
image/*
  image/gif : .gif 
  image/png : .png 
  image/jpeg : .jpg .jpeg .jpe 

読み取り専用

DictionaryにはList.AsReadOnlyメソッドのようなメソッドが存在しないため、読み取り専用のDictionaryを作成する手段が存在しません。 .NET Framework 4.5からはReadOnlyDictionaryが導入されましたが、それ以前のバージョンでは使用することができないため、読み取り専用のDictionaryが必要な場合は自前で実装する必要があります。

次の例はそのような読み取り専用のクラスReadOnlyDictionaryを実装した例です。 動作やスローする例外等はReadOnlyCollectionに近いものとなるようにしています。 また、IDictionaryに対して拡張メソッドAsReadOnlyを追加するためのクラスIDictionaryExtensionsも作成しています。 なお、この例で実装しているReadOnlyDictionaryクラスでは元のDictionaryを読み取り専用にする訳ではないので注意してください。 元になったDictionaryは、依然として要素の追加・削除・変更が可能です。

ReadOnlyDictionaryの実装例
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    Dictionary<string, int> dict = new Dictionary<string, int>();

    dict["foo"] = 16;
    dict["bar"] = 72;

    // 読み取り専用のIDictionaryを作成する
    IDictionary<string, int> readOnlyDict = dict.AsReadOnly();

    foreach (KeyValuePair<string, int> pair in readOnlyDict) {
      Console.WriteLine("{0} => {1}", pair.Key, pair.Value);
    }

    // 既に存在するキーの値の変更を試みる
    readOnlyDict["foo"] = 42;
  }
}

// IDictionaryに拡張メソッドAsReadOnlyを追加するためのクラス
public static class IDictionaryExtensions {
  public static IDictionary<TKey, TValue> AsReadOnly<TKey, TValue>(this IDictionary<TKey, TValue> dictionary)
  {
    if (dictionary == null)
      throw new ArgumentNullException("dictionary");
    else if (dictionary.IsReadOnly)
      return dictionary;
    else
      return new ReadOnlyDictionary<TKey, TValue>(dictionary);
  }
}

// IDictionaryをラップして読み取りアクセスのみ許可するようにIDictionaryを実装したクラス
public class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue> {
  // ラップするIDictionary
  private IDictionary<TKey, TValue> dictionary;

  public TValue this[TKey key] {
    get { return dictionary[key]; }
    set { throw ReadOnlyException(); }
  }

  public ICollection<TKey> Keys {
    get { return dictionary.Keys; }
  }

  public ICollection<TValue> Values {
    get { return dictionary.Values; }
  }

  public int Count {
    get { return dictionary.Count; }
  }

  public bool IsReadOnly {
    get { return true; }
  }

  public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary)
  {
    if (dictionary == null)
      throw new ArgumentNullException("dictionary");

    this.dictionary = dictionary;
  }

  /*
   * 元のDictionaryに対する読み取りのみが行われる操作
   */
  public bool Contains(KeyValuePair<TKey, TValue> item)
  {
    return dictionary.Contains(item);
  }

  public bool ContainsKey(TKey key)
  {
    return dictionary.ContainsKey(key);
  }

  public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
  {
    dictionary.CopyTo(array, arrayIndex);
  }

  public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
  {
    return dictionary.GetEnumerator();
  }

  System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  {
    return dictionary.GetEnumerator();
  }

  public bool TryGetValue(TKey key, out TValue @value)
  {
    return dictionary.TryGetValue(key, out @value);
  }

  /*
   * 元のDictionaryに対する変更が行われる操作
   */
  public void Add(KeyValuePair<TKey, TValue> item)
  {
    throw ReadOnlyException();
  }

  public void Add(TKey key, TValue @value)
  {
    throw ReadOnlyException();
  }

  public void Clear()
  {
    throw ReadOnlyException();
  }

  public bool Remove(KeyValuePair<TKey, TValue> item)
  {
    throw ReadOnlyException();
  }

  public bool Remove(TKey key)
  {
    throw ReadOnlyException();
  }

  private Exception ReadOnlyException()
  {
    return new NotSupportedException("コレクションは読み取り専用です");
  }
}
ReadOnlyDictionaryの実装例
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim dict As New Dictionary(Of String, Integer)()

    dict("foo") = 16
    dict("bar") = 72

    ' 読み取り専用のIDictionaryを作成する
    Dim readOnlyDict As IDictionary(Of String, Integer) = dict.AsReadOnly()

    For Each pair As KeyValuePair(Of String, Integer) In readOnlyDict
      Console.WriteLine("{0} => {1}", pair.Key, pair.Value)
    Next

    ' 既に存在するキーの値の変更を試みる
    readOnlyDict("foo") = 42
  End Sub
End Class

' IDictionaryに拡張メソッドAsReadOnlyを追加するためのモジュール
Public Module IDictionaryExtensions
  <System.Runtime.CompilerServices.Extension()> _
  Public Function AsReadOnly(Of TKey, TValue)(ByVal dictionary As IDictionary(Of TKey, TValue)) As IDictionary(Of TKey, TValue)
    If dictionary Is Nothing Then
      Throw New ArgumentNullException("dictionary")
    Else If dictionary.IsReadOnly Then
      Return dictionary
    Else
      Return New ReadOnlyDictionary(Of TKey, TValue)(dictionary)
    End If
  End Function
End Module

' IDictionaryをラップして読み取りアクセスのみ許可するようにIDictionaryを実装したクラス
Public Class ReadOnlyDictionary(Of TKey, TValue)
  Implements IDictionary(Of TKey, TValue)

  ' ラップするIDictionary
  Private dictionary As IDictionary(Of TKey, TValue)

  Public Default Property Item(ByVal key As TKey) As TValue Implements IDictionary(Of TKey, TValue).Item
    Get
      Return dictionary(key)
    End Get
    Set (ByVal val As TValue)
      Throw ReadOnlyException()
    End Set
  End Property

  Public ReadOnly Property Keys As ICollection(Of TKey) Implements IDictionary(Of TKey, TValue).Keys
    Get
      Return dictionary.Keys
    End Get
  End Property

  Public ReadOnly Property Values As ICollection(Of TValue) Implements IDictionary(Of TKey, TValue).Values
    Get
      Return dictionary.Values
    End Get
  End Property

  Public ReadOnly Property Count As Integer Implements IDictionary(Of TKey, TValue).Count
    Get
      Return dictionary.Count
    End Get
  End Property

  Public ReadOnly Property IsReadOnly As Boolean Implements IDictionary(Of TKey, TValue).IsReadOnly
    Get
      Return True
    End Get
  End Property

  Public Sub New(ByVal dictionary As IDictionary(Of TKey, TValue))
    If dictionary Is Nothing Then Throw New ArgumentNullException("dictionary")

    Me.dictionary = dictionary
  End Sub

  '
  ' 元のDictionaryに対する読み取りのみが行われる操作
  '
  Public Function Contains(ByVal item As KeyValuePair(Of TKey, TValue)) As Boolean Implements IDictionary(Of TKey, TValue).Contains
    Return dictionary.Contains(item)
  End Function

  Public Function ContainsKey(ByVal key As TKey) As Boolean Implements IDictionary(Of TKey, TValue).ContainsKey
    Return dictionary.ContainsKey(key)
  End Function

  Public Sub CopyTo(ByVal array() As KeyValuePair(Of TKey, TValue), ByVal arrayIndex As Integer) Implements IDictionary(Of TKey, TValue).CopyTo
    dictionary.CopyTo(array, arrayIndex)
  End Sub

  Public Function GetEnumerator() As IEnumerator(Of KeyValuePair(Of TKey, TValue)) Implements IEnumerable(Of KeyValuePair(Of TKey, TValue)).GetEnumerator
    Return dictionary.GetEnumerator()
  End Function

  Private Function GetNonGenericEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
    Return dictionary.GetEnumerator()
  End Function

  Public Function TryGetValue(ByVal key As TKey, ByRef value As TValue) As Boolean Implements IDictionary(Of TKey, TValue).TryGetValue
    Return dictionary.TryGetValue(key, value)
  End Function

  '
  ' 元のDictionaryに対する変更が行われる操作
  '
  Public Sub Add(ByVal item As KeyValuePair(Of TKey, TValue)) Implements IDictionary(Of TKey, TValue).Add
    Throw ReadOnlyException()
  End Sub

  Public Sub Add(ByVal key As TKey, ByVal value As TValue) Implements IDictionary(Of TKey, TValue).Add
    Throw ReadOnlyException()
  End Sub

  Public Sub Clear() Implements IDictionary(Of TKey, TValue).Clear
    Throw ReadOnlyException()
  End Sub

  Public Function Remove(ByVal item As KeyValuePair(Of TKey, TValue)) As Boolean Implements IDictionary(Of TKey, TValue).Remove
    Throw ReadOnlyException()
  End Function 

  Public Function Remove(ByVal key As TKey) As Boolean Implements IDictionary(Of TKey, TValue).Remove
    Throw ReadOnlyException()
  End Function 

  Private Function ReadOnlyException() As Exception
    Return New NotSupportedException("コレクションは読み取り専用です")
  End Function
End Class
実行結果
foo => 16
bar => 72

ハンドルされていない例外: System.NotSupportedException: コレクションは読み取り専用です
   場所 ReadOnlyDictionary`2.set_Item(TKey key, TValue val)
   場所 Sample.Main()