- ジェネリックコレクション ページ一覧
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であれば、
となります。
Dictionaryを使う場合には、最初に格納する要素を指定する(§.コレクション初期化子)ことも、空のDictionaryを作成しておいて後から要素を追加することも出来ます。 以下の例では空のDictionaryを作成して使うことにします。
Dictionaryに格納されている値を取得(参照)するには、インデックスを指定するかわりに配列と同様の記法でキーを指定します。 そうすることにより、Dictionaryに格納されているペアの中からキーに対応する値を取得することが出来ます。 また、取得だけでなく、キーに対応する特定の要素を設定(値の上書き)をすることも出来ます。
C#ではインデクサによって、VB.NETでは既定のプロパティItemによって、配列と同様にインデックスによる取得・設定ができるようになっています。 インデクサについてはプロパティ §.インデクサを参照してください。
なお、現在Dictionary内に含まれている要素の数を取得するにはLengthプロパティではなくCountプロパティを参照します。
上記サンプルで作成されるDictionaryの内容を表にすると次のようになります。
キー | 値 |
---|---|
foo | 16 |
bar | 72 |
baz | 42 |
Dictionaryに対して存在しないキーを指定した場合は例外KeyNotFoundExceptionがスローされます。
この動作は、存在しないキーに対してnull/Nothingが返されるHashtableとは異なるので注意してください。 Dictionaryでは、インデクサで取得した値からキーが存在するかどうかを確認することはできません。 事前にキーが存在するかどうかを確かめるにはContainsKeyメソッドを使うことが出来ます。
要素の追加
既に例に挙げた通りインデクサでキーを指定することでDictionaryに要素を設定(追加)することができますが、これとは別にAddメソッドを使うことでもDictionaryに要素を追加することが出来ます。
一見するとインデクサを使う場合とAddメソッドを使う場合ではどちらも変わらないように見えますが、既にキーが存在する場合の動作が異なります。 インデクサでは既にキーが存在している場合は値が上書きされるのに対し、Addメソッドでは既にキーが存在している場合は例外ArgumentExceptionがスローされます。
このように、インデクサでは指定されたキーが存在しているかどうかに関わらず値が上書きされて設定されるのに対し、Addメソッドではキーが既に存在しているかを検査した上で値の追加が行われます。 事前にキーが存在するかどうかを確かめるにはContainsKeyメソッドを使います。
コレクション初期化子
Dictionaryを使用する場合、インスタンスの作成後に要素の追加を行う場合が多々あります。 コレクション初期化子を使うと、インスタンスの作成と要素の追加を同時に行うことができます。 コレクション初期化子はC# 3.0(Visual C# 2008)以降、VB 10(Visual Basic 2010)以降でサポートされます。
コレクション初期化子では、{ {キー1, 値1}, {キー2, 値2}, ... }
のように追加する要素をペア毎に中括弧で括って記述します。 RubyやPerlのハッシュで使われる{キー1 => 値1, キー2 => 値2, ...}
といった形式はコレクション初期化子ではサポートされません。
要素の削除
Dictionaryから要素を削除する場合は、Removeメソッドを使います。 このメソッドでは、指定されたキーを持つ要素をDictionaryから削除し、戻り値として実際に削除に成功したかどうかがbool/Boolean型で返されます。 Addメソッドとは異なり、Removeメソッドに指定したキーを持つ要素が存在しない場合でも例外はスローされません(この場合、戻り値としてfalseが返されます)。
Dictionary内のすべての要素を削除するにはClearメソッドを使うことが出来ます。
なお、インデクサを使って値にnull/Nothingを設定しても要素自体を削除することはできません。 この場合、指定されたキーに対応する値としてnull/Nothingが上書き設定されるだけで、要素自体は削除されません。 特にVBでは、Dictionaryの値の型が値型の場合、値としてNothingを設定すると、デフォルト値が設定される点に注意が必要です(型の種類・サイズ・精度・値域 §.型のデフォルト値、値型と参照型)。
値に対する変更
値に構造体を格納するようにしたDictionaryで構造体のプロパティやフィールドだけを変更しようとする場合は注意が必要です。 例えば、次のようなコードはコンパイルエラーとなります。
このような操作を行いたい場合は、いったんDictionaryから一時変数に構造体をコピーし、値を変更してから再度Dictionaryに格納するようにします。
構造体ではなくクラスのインスタンスを格納する場合はこのような操作を行う必要はありません。 この違いは型が値型か参照型かによって決まります。 詳しくは値型と参照型 §.値型のプロパティ・インデクサを参照してください。
要素の列挙
DictionaryとKeyValuePair
列挙について解説する前に、Dictionaryの内部構造とKeyValuePair構造体について触れておきます。 Dictionaryでは、要素を格納する際、キーと値の組み合わせをKeyValuePairとして格納します。 KeyValuePairはその名の通りキーとそれに対応する値のペアを表す構造体で、キーを格納するプロパティKeyと値を格納するプロパティValueの二つのメンバを持ちます。 Dictionaryの内部では、キーと値の組み合わせはKeyValuePairで管理されます。
キー | 値 |
---|---|
"foo" | 16 |
"bar" | 72 |
"baz" | 42 |
: : |
: : |
... | ... |
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を列挙した場合はキーと値のペアで取得されることになりますが、キーだけ・値だけをそれぞれ個別に列挙することも出来ます。 Keysプロパティを参照することでDictionaryに格納されているすべてのキーを、Valuesプロパティを参照することですべての値をそれぞれ取得・列挙することが出来ます。
要素の列挙と順序
Dictionaryは順序を持たないコレクションであるため、List等とは異なり要素を列挙する場合は必ずしも追加した順で列挙されるとは限らない点に注意が必要です。 このことは、ドキュメント上では以下のように記述されています。
列挙処理のために、ディクショナリ内の各アイテムは、値とそのキーを表す KeyValuePair(Of TKey, TValue) 構造体として処理されます。 アイテムが返される順序は未定義です。
Dictionary(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.
そのため、foreach文でDictionaryの列挙を行う場合は、列挙される順序に依存した処理を記述しないようにする必要があります。
列挙される順序が意味を持つ場合は、Dictionaryの代わりにSortedListクラス・SortedDictionaryクラスを使うことができます。 SortedList・SortedDictionaryでは、常に一定の順序で列挙されることが保証されます。 また、非ジェネリックコレクションであるOrderedDictionaryクラスでは、要素を追加した順序が維持され、列挙の際も追加された順となることが保証されます。
このほか、LINQの拡張メソッドOrderByを使うことにより、順序を定義して要素の列挙を行うこともできます。
OrderByメソッドについて詳しくは基本型のソートと昇順・降順でのソート §.Enumerable.OrderByで解説しています。
OrderedDictionaryクラスについてはOrderedDictionaryクラスを、またこれに関連してジェネリック版OrderedDictionaryを参照してください。
列挙中の要素の追加・削除
Dictionaryをforeach文で列挙している最中に要素の追加や削除などの変更を行おうとすると、例外InvalidOperationExceptionがスローされます。
例外が発生する理由や回避する方法については列挙操作中のコレクションの変更でも解説していますが、Dictionaryクラスではインデックスを指定した列挙操作は行えないため、ここで紹介している方法は適用できません。
先の例のように条件に合う要素を検索して削除するといった場合は、列挙による要素の検索と削除を別々のforeach文で行うようにすることでInvalidOperationExceptionの発生を回避することが出来ます。
また別の方法として、次の例のように別のDictionaryを用意し、元のDictionaryから条件に合う要素だけを別のDictionaryコピーすることで要素を削除した場合と同等のDictionaryを得るという方法もあります。
特定要素を除外した列挙が行えればよいのであれば、LINQの拡張メソッドWhereを使って次のようにすることもできます。
要素の並べ替え (ソート)
Dictionaryは順序を持たないことから、Dictionaryをソートされた状態にすることはできません。 従ってSort()のようなメソッドも用意されていません。 Dictionary内の各ペアをソートされた状態にしたい場合は、次の例のように一旦Listに変換し、List.Sortメソッドを使って並べ替えを行う、といった方法をとる必要があります。
このほか、Dictionaryをソートする方法については基本型のソートと昇順・降順でのソート §.Dictionaryのソートで詳しく解説しています。
要素を常に並べ替えた状態にしておきたい場合や、格納される要素の順序が重要になる場合は、Dictionaryの代わりにSortedListやSortedDictionaryを使うことも出来ます。
要素の検索
キー・値の有無チェック (ContainsKey, ContainsValue)
Dictionaryに指定したキーが存在するかどうかを調べるにはContainsKeyメソッド、値が存在するかどうかを調べるにはContainsValueメソッドを使います。
キーの有無チェックと値の取得 (TryGetValue)
Dictionaryでは、キーが存在しない場合は例外KeyNotFoundExceptionがスローされるため、事前にキーがあるかどうか調べるなどする必要があります。 しかし、TryGetValueメソッドを使うとキーの有無チェックと値の取得を同時に行えるため、キーの有無チェックやKeyNotFoundExceptionのキャッチをする必要が無くなります。
このメソッドは、ContainsKeyメソッドと値の取得を組み合わせた動作をするメソッドと言えます。 キーの有無はTryGetValueメソッドの戻り値で取得でき、該当するキーが存在する場合はキーに対応する値がoutパラメータに格納されます。
値からキーの逆引き検索
Dictionaryには値から対応するキーを逆引きするメソッドは用意されていません。 そのため、次の例のようにDictionaryの全要素を列挙して該当するKeyValuePairを検索するよう独自に実装する必要があります。
LINQの拡張メソッドFirstを使って逆引きする場合は次のように記述できます。
最初の要素・最後の要素
§.要素の列挙と順序でも述べたようにDictionaryに格納される要素には順序の概念が無いため、最初の要素・最後の要素は定義することができず、そういった要素を取得するプロパティ等も提供されません。
LINQの拡張メソッドOrderByで並べ替えを行い、その中から最初の要素・最後の要素を取得したい場合には同じく拡張メソッドのFirstメソッド・Lastメソッドを使うことができます。
Dictionaryとキー
キー比較のカスタマイズ(大文字小文字の違いの無視)
Hashtableと同様、コンストラクタで適切なIEqualityComparer<T>インターフェイスを指定することで、Dictionaryのキー比較時の動作をカスタマイズ出来ます。 例えば、文字列をキーとした場合に大文字小文字を無視するようにするといったことが出来ます。 以下は、StringComparerクラスを使って、大文字小文字を無視するDictionaryを作成する例です。
キーと複合型
Dictionaryクラスでは、キーとなる型がEqualsメソッドとGetHashCodeメソッドをオーバーライドしていて、かつ適切な値を返すように実装されていないと正しく動作しません。 キーに独自に定義した型を指定してDictionaryを使用したい場合は、これらのメソッドをオーバーライドして適切に実装するか、DictionaryのコンストラクタにIEqualityComparer<T>インターフェイスを指定する必要があります。
IEqualityComparer<T>インターフェイスの実装方法や、独自に定義した型をキーにする例については等価性の定義と比較で解説しています。
Dictionaryとインデックス
Dictionaryでは格納される要素はキーによってのみ管理されます。 また順序を持たないコレクションであるためインデックスという概念を持たず、インデックスを指定したキー・値の参照・設定などの操作も提供されません。 これに従って、for文による列挙も行えません。 当然、IndexOfKey・IndexOfValueといったキー・値のインデックスを取得するような操作も提供されません。
インデックスを指定した操作
インデックスを使った操作を行いたい場合は、次の例のように一旦DictionaryからListに変換する、といった方法を取る必要があります。
キーもしくは値を配列にコピーしてから列挙したい場合は、次の例のようにKeysプロパティ・Valuesプロパティを参照してCopyToメソッドによって配列にコピーした上で列挙するという方法がとれます。
キーの型をintなどの整数型にすることでインデックスを持たせたDictionaryのように扱うことはできますが、§.要素の列挙と順序でも述べたようにDictionaryに格納された要素をforeach文で列挙する際の順序は不定である点、インデックス(キー)が不連続な値となりうる点などから、あまり推奨できる方法ではありません。
SortedList
SortedListを使うと、キーと値のペアにインデックスを持たせることができます。 ただし、SortedListではインデックスを指定して要素を格納することはできません。 インデックスは常にキーに従って要素を並べ替えた時の順序で自動的に割り振られます。
OrderedDictionary
System.Collections.Specialized名前空間のOrderedDictionaryクラスでは、要素が格納された順にインデックスが割り振られ、列挙する際も常に格納された順で列挙されます。 ただし、OrderedDictionaryは非ジェネリックなコレクションです。 OrderedDictionaryに相当するジェネリックコレクションは用意されていません。
OrderedDictionaryクラスについてはOrderedDictionaryクラスを、またこれに関連してジェネリック版OrderedDictionaryを参照してください。
入れ子
Listや他のジェネリックコレクション同様、Dictionaryもキー・値ともに任意の型に型付けすることができるため、キー・値に他のジェネリックコレクション型を格納するようにすることも出来ます。 次の例では、Dictionaryクラスを使って文字列の辞書を処理するために入れ子になったDictionary<char, List<string>>を作成し、要素の追加・削除・列挙などの操作を行っています。
同様に、DictionaryのDictionaryを作成することも可能です。
読み取り専用
DictionaryにはList.AsReadOnlyメソッドのようなメソッドが存在しないため、読み取り専用のDictionaryを作成する手段が存在しません。 .NET Framework 4.5からはReadOnlyDictionaryが導入されましたが、それ以前のバージョンでは使用することができないため、読み取り専用のDictionaryが必要な場合は自前で実装する必要があります。
次の例はそのような読み取り専用のクラスReadOnlyDictionaryを実装した例です。 動作やスローする例外等はReadOnlyCollectionに近いものとなるようにしています。 また、IDictionaryに対して拡張メソッドAsReadOnlyを追加するためのクラスIDictionaryExtensionsも作成しています。 なお、この例で実装しているReadOnlyDictionaryクラスでは元のDictionaryを読み取り専用にする訳ではないので注意してください。 元になったDictionaryは、依然として要素の追加・削除・変更が可能です。