Dictionaryクラス

Dictionaryクラスとは

データの集合を扱う場合に、配列Listクラスに次いで使用頻度が高いのがDictionaryクラスです。
Dictionaryクラスは辞書クラス辞書配列などと呼ばれるほか、連想配列とも呼ばれます。
配列やListクラスは、要素アクセスに使用する添え字には数値を用いますが、Dictionaryクラスは任意のデータ型を使用します。


using System;
using System.Collections.Generic; //←これが必要

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            //Dictionaryクラス
            Dictionary<string, int> dct = new Dictionary<string, int>();

            //要素を追加
            dct.Add("Apple", 120);
            dct.Add("Grape", 220);
            dct.Add("Orange", 90);

            //「Apple」の要素を表示
            Console.WriteLine(dct["Apple"]);

			//要素をすべて表示
            foreach(KeyValuePair<string, int> kvp in dct)
            {
                Console.WriteLine("{0}: {1}", kvp.Key, kvp.Value);
            }

            Console.ReadLine();
        }
    }
}
120
Apple: 120
Grape: 20
Orange: 90

上記のサンプルコードでは、要素へのアクセスに文字列(string型)を使用しています。

Listクラスと同じく、Dictionaryクラスを使用するにはコードの先頭にusing System.Collections.Generic;と記述する必要があります。

これは名前空間の導入なので、必須ではありませんが記述しないと完全修飾名で指定する必要があります。
詳しくは→名前空間を参照してください。

なお、最近のバージョンのVisual Studioでは暗黙的なusingディレクティブが有効な場合があります。

Dictionaryクラスの使用宣言

Dictionaryクラスは以下の形式で宣言します。


Dictionary<データ型1, データ型2> 変数名 = new Dictionary<データ型1, データ型2>();

Dictionary<データ型1, データ型2> 変数名 = new Dictionary<データ型1, データ型2>() { 初期化子 };

Dictionary<データ型1, データ型2> 変数名 = new Dictionary<データ型1, データ型2> { 初期化子 };

基本的にListクラスの宣言と同じですが、Dictionaryクラスは型引数(データ型の指定)がふたつ必要です。

配列やListクラスは、各要素のアクセスのための添え字には数値を使用します。
その数値は要素の先頭から順に自動的に割り振られます。

Dictionaryクラスは添え字には任意の値を指定します。
ただの数値でも構いませんし、文字列でも構いません。
添え字の値は自動では割り振られないので、要素の追加と同時に添え字も一緒に指定します。
サンプルコードでは文字列を添え字にしています。

「データ型1」には添え字に使用するデータ型を指定します。
これをキー(Key)といいます。
「データ型2」には格納される要素(値、Value)のデータ型を指定します。

宣言と同時に初期化

初期化子を使用する場合は以下のようにします。


Dictionary<string, int> dct = new Dictionary<string, int>()
{
//  { 添え字, 要素 },
    { "Apple", 120 },
    { "Grape", 220 },
    { "Orange", 90 },
};

要素の追加と要素へのアクセス

要素の追加は添え字演算子(角括弧[])のほか、Addメソッドで行えます。


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

//「Apple」という名前で「120」を追加
dct["Apple"] = 120;

//「Grape」という名前で「220」を追加
dct.Add("Grape", 220);

//「Apple」のデータの取り出し
int num = dct["Apple"]; //120

//「Apple」のデータを別の値で上書き
dct["Apple"] = 150;

Listクラスは要素の追加には専用のメソッドが必要ですが、Dictionaryクラスは存在しないキーに値を代入することで直接要素を追加できます。
指定のキーがすでに存在する場合は値を上書きします。

Addメソッドは、第一引数にはキーを、第二引数には値を指定します。

値の取り出しは配列と同じく添え字演算子(角括弧[])を使用します。
キーにstring型を使用していれば、文字列で各要素にアクセスすることになります。
このように、「キー」と「値」をペアにして扱えるのが連想配列の最大の特徴です。

キーにint型を使用した場合は配列のように数値でアクセスすることになります。


Dictionary<int, int> dct = new Dictionary<int, int>();

dct.Add(1, 120);

int num = dct[1]; //120

//「0」は存在しない!
int num2 = dct[0]; //エラー

ただし配列とは違い、先頭から順に数値が割り振られるわけではないので、for文で各要素にアクセスすることはできません。
数値を連番で割り当てていけば可能ですが、そのようなアクセスをしたいならListクラスや配列を使用すべきです。
(foreach文は使用できます)

存在しないキーの要素を取り出そうとするとエラー(例外)になります。

Addメソッドは既存のKeyを上書ききない

Dictionaryクラスはキーで要素にアクセスするため、同じキーを持つ要素を同時に保存することはできません。
既に存在するキーに対しては値の上書きになりますが、Addメソッドでは上書き操作はでず、エラー(例外)になります。


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

dct.Add("Apple", 120);
dct.Add("Apple", 150); //エラー

//これはOK
//「150」で上書き
dct["Apple"] = 150;

なお、キーに文字列を使用する場合、大文字と小文字は区別されます。
ややこしいのであまりおすすめしません。


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

dct.Add("Apple", 120);
dct.Add("apple", 150); //これはOK

キーにnullは指定できない

キーや値には参照型を使用できますが、キーにnullは指定できません。
値にはnullを格納できます。


Dictionary<string, string> dct = new Dictionary<string, string>();

//エラー
dct.Add(null, "a");

//OK
dct.Add("b", null);

すべての要素の列挙

Dictionaryクラスはユーザーが自らキー(要素名)を定義するので、仮にint型をキーに指定していてもその数値が連番で並んでいることが保証されているわけではありません。
そのためfor文で各要素にアクセスすることはできません。

Dictionaryクラスの要素を列挙するにはforeach文を使用します。


Dictionary<string, int> dct = new Dictionary<string, int>(){
    { "Apple", 120 },
    { "Grape", 220 },
    { "Orange", 90 },
};

foreach (KeyValuePair<string, int> kvp in dct)
{
    Console.WriteLine("{0}: {1}", kvp.Key, kvp.Value);
}
Apple: 120
Grape: 220
Orange: 90

Dictionaryクラスの要素はKeyValuePairという構造体で、foreach文で値を取り出すとこれが取り出されます。
(→構造体)

KeyValuePair構造体はKeyValueというふたつのプロパティを持ちます。
これはDictionaryクラスで定義したのと同じで、Keyは要素名、Valueは値です。
それぞれのデータ型はDictionaryクラスで定義した通りの型になります。

foreach文の繰り返し変数は変更できないので、この方法ではDictionaryの値を変更することはできません。
値を書き換える方法は後述します。

並び順は不定

DictionaryクラスはListクラスなどとは違い、要素の並び順という概念はありません。
つまりforeach文などで取り出される値の順番は不定です。
(追加した順とは限らない)
データが順番通りに並んでいることを前提とするプログラムはバグの原因となります。
順番通りに並ぶ必要がある場合はListクラスなどを使用します。

型推論

Dictionaryクラスは記述が長くなりがちなので型推論を使用するのが良いでしょう。


var dct = new Dictionary<string, int>()
{
    { "Apple", 120 },
    { "Grape", 220 },
    { "Orange", 90 },
};

foreach (var kvp in dct) //KeyValuePair<string, int>
{
    Console.WriteLine("{0}: {1}", kvp.Key, kvp.Value);
}

要素数

Dictionaryクラスの要素数はCountプロパティをで取得します。


Dictionary<string, int> dct = new Dictionary<string, int>(){
    { "Apple", 120 },
    { "Grape", 220 },
    { "Orange", 90 },
};

Console.WriteLine(dct.Count); //3

Key、Valueをすべて取得

Dictionaryクラスからキーの一覧を取り出すにはKeysプロパティを使用します。
値の一覧を取り出すにはValuesプロパティを使用します。


var dct = new Dictionary<string, int>(){
    { "Apple", 120 },
    { "Grape", 220 },
    { "Orange", 90 },
};

//Keyをすべて取得
Dictionary<string, int>.KeyCollection keys = dct.Keys;

//Valueをすべて取得
Dictionary<string, int>.ValueCollection values = dct.Values;

foreach (string k in keys)
{
    Console.WriteLine(k);
}

foreach (int v in values)
{
    Console.WriteLine(v);
}
Apple
Grape
Orange
120
220
90

Keysプロパティで得られるのはKeyCollectionValuesプロパティで得られるのはValueCollectionというそれぞれ専用のデータ型です。
これはそのままでは各データを取り出すことはできません。
(foreach文ですべて列挙することはできます)

CopyTo

各コレクションからデータを取り出すにはCopyToメソッドを使用して配列に変換します。


var dct = new Dictionary<string, int>(){
    { "Apple", 120 },
    { "Grape", 220 },
    { "Orange", 90 },
};

string[] keys = new string[dct.Count];
int[] values = new int[dct.Count];

dct.Keys.CopyTo(keys, 0);
dct.Values.CopyTo(values, 0);

Console.WriteLine(keys[0]);
Console.WriteLine(values[0]);
Apple
120

第一引数にはコピー先の配列を指定します。
第二引数はコピー先配列のコピー開始番号を指定します。
コピー先配列の容量が足りないとエラー(例外)になります。

CopyToメソッドで得られるのはただの配列ですから、数値で各要素にアクセスできます。

ToList、ToArray

KeysValuesプロパティはToListメソッドで全ての要素をListに変換できます。
ToArrayメソッドで全ての要素を配列に変換できます。
これらのメソッドの使用にはコードの先頭にusing System.Linq;が必要です。


using System;
using System.Collections.Generic;
using System.Linq; //←これが必要

namespace ConsoleApplication1
{
//以降省略

var dct = new Dictionary<string, int>(){
    { "Apple", 120 },
    { "Grape", 220 },
    { "Orange", 90 },
};

var keys = dct.Keys.ToList();
var values = dct.Values.ToList();

Console.WriteLine(keys[0]);
Console.WriteLine(values[0]);
Apple
120

特に理由がなければ簡潔に書けるこちらの方が良いでしょう。

foreach文で要素を書き換え

Dictionaryクラスはそのままではforeach文で取り出した値を書き換えることはできません。
値を書き換える場合はKeysプロパティを利用します。


var dct = new Dictionary<string, int>(){
    { "Apple", 120 },
    { "Grape", 220 },
    { "Orange", 90 },
};

//Keysプロパティをリストに変換
var keys = dct.Keys.ToList();

foreach (var key in keys)
{
    dct[key] *= 2; //要素の書き換え
}

foreach (var kvp in dct)
{
    Console.WriteLine("{0}: {1}", kvp.Key, kvp.Value);
}
Apple: 240
Grape: 440
Orange: 180

まずDictionaryのKeysプロパティを配列やリストに変換します。
これはforeach文で列挙中のコレクションを書き換えるとエラーになるためで、元のDictionaryクラスとは独立したオブジェクトにしておきます。

foreach文では、このKeysリスト(または配列)を列挙します
あとはループ内で取り出されたキーをDictionaryクラスの添え字に指定し、各要素の値を書き換えます。