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

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

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

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」には添え字に使用するデータ型を指定します。
「データ型2」には格納される要素のデータ型を指定します。

添え字の値をKey、格納される要素を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は存在しないKeyに値を代入することで直接要素を追加できます。
指定のKeyがすでに存在する場合はValueを上書きします。

Addメソッドは、第一引数にはKeyを、第二引数にはValueを指定します。
それぞれのデータ型は最初に宣言した型となります。

各要素へのアクセスは配列と同じく添え字演算子(角括弧[])を使用しますが、添え字として使用するのはKeyです。
Keyにstring型を使用していれば、文字列で各要素にアクセスすることになります。
KeyとValueを関連づけできるのが連想配列の最大の特徴です。

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


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

dct.Add(1, 120);

int num! = dct[1]; //120

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

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

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

Addメソッドでは既存のKeyを指定できない

DictionaryクラスはKeyで要素にアクセスするため、同じKeyを持つ要素を同時に保存することはできません。
すでに存在するKeyをAddメソッドで追加しようとするとエラー(例外)になります。
(上書きにはならない)


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

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

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

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


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

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

すべての要素の列挙

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

Dictionaryクラスの要素を列挙するにはforeach文を使用します。
foreach文によって取り出される各要素はKeyValuePairという特殊なデータ型となります。
(構造体というものです)


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);
}

Dictionaryクラスは実はKeyValuePair構造体の集合です。
Dictionaryクラスをforeachで値を取り出すとKeyValuePair構造体が得られます。

KeyValuePairはKeyValueというふたつのプロパティを持ちます。
これはDictionaryクラスで定義したのと同じで、Keyは要素名、Valueは値です。
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クラスからKeyの一覧を取り出すにはKeysプロパティを使用します。
Valueの一覧を取り出すには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プロパティで得られるのはKeyCollection、Valuesプロパティで得られるのは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

ToListメソッドを使用するとそのままListが得られ、配列を用意する手間が省けます。
ただしToListの使用にはコードの先頭に「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リスト(または配列)を列挙します
あとはループ内で取り出されたKeyをDictionaryクラスの添え字に指定し、各要素の値を書き換えます。