C#のMapはDictionaryで解決!基本の使い方からキー検索・追加・更新まで徹底解説

はじめに

C#で「Mapを使いたい」と思ったとき、多くの場合はDictionary<TKey, TValue>を使えば解決できます。

Javaではキーと値のペアを扱うコレクションとしてMapがよく使われますが、C#では同じような役割を持つものとしてDictionaryが用意されています。

たとえば、次のように「ユーザーIDからユーザー名を取得する」「商品コードから価格を取得する」「設定名から設定値を取得する」といった処理に向いています。

C#
var users = new Dictionary<int, string>();

users.Add(1, "田中");
users.Add(2, "佐藤");

Console.WriteLine(users[1]); // 田中

この記事では、c# map dictionaryで調べている方向けに、C#におけるMap相当の機能であるDictionaryについて、基本構文、追加、検索、更新、削除、ループ処理、よくあるエラー、応用的な使い方まで順番に解説します。

1. C#にMapはある?Dictionaryで解決できる理由

1-1. C#にはJavaのMapに相当するDictionaryがある

C#には、JavaのMapという名前の標準コレクションはありません。

その代わりに、キーと値のペアを管理するための代表的なコレクションとしてDictionary<TKey, TValue>があります。

Javaで次のように書く処理は、

Java
Map<Integer, String> users = new HashMap<>();
users.put(1, "田中");

C#では次のように書きます。

C#
var users = new Dictionary<int, string>();
users.Add(1, "田中");

C#では「Map」という名前ではなく「Dictionary」という名前で覚えるとよいでしょう。

Dictionaryは、キーを使って値を高速に取得できるコレクションです。たとえば、ID、コード、名前、設定キーなどをもとに、対応する値を取り出したいときに便利です。

1-2. MapとDictionaryの違いを初心者向けに整理

JavaのMapとC#のDictionaryは、基本的な考え方はよく似ています。

どちらも「キー」と「値」をセットで管理します。

C#
キー    
1 "田中"
2 "佐藤"
3 "鈴木"

C#のDictionary<TKey, TValue>では、TKeyにキーの型、TValueに値の型を指定します。

C#
Dictionary<int, string>

これは「キーがint、値がstringのDictionary」という意味です。

JavaのMap<K, V>に慣れている人であれば、C#のDictionary<TKey, TValue>はほぼ同じ用途で使えると考えて問題ありません。

ただし、メソッド名や細かい挙動には違いがあります。

たとえば、Javaでは値の追加にputを使うことが多いですが、C#ではAddやインデクサを使います。

C#
users.Add(1, "田中");
users[2] = "佐藤";

1-3. Dictionaryを使うべきケース

Dictionaryは、キーを使って値をすばやく取得したい場合に向いています。

たとえば、次のようなケースです。

C#
var prices = new Dictionary<string, int>
{
{ "A001", 1200 },
{ "B002", 2500 },
{ "C003", 980 }
};

Console.WriteLine(prices["B002"]); // 2500

商品コードから価格を取り出す場合、Listや配列で毎回検索すると、データ数が増えるほど処理が面倒になります。

一方、Dictionaryならキーを指定するだけで目的の値を取得できます。

C#
int price = prices["A001"];

次のようなデータ管理では、Dictionaryが特に便利です。

ユーザーIDとユーザー名、商品コードと価格、設定名と設定値、都道府県コードと都道府県名、エラーコードとメッセージなどです。

1-4. Listや配列ではなくDictionaryを選ぶメリット

Listや配列は、複数の値を順番に管理するのに向いています。

C#
var names = new List<string> { "田中", "佐藤", "鈴木" };

この場合、特定の名前を探すにはループ処理が必要になることがあります。

C#
foreach (var name in names)
{
if (name == "佐藤")
{
Console.WriteLine("見つかりました");
}
}

一方、Dictionaryはキーを使って直接値にアクセスできます。

C#
var users = new Dictionary<int, string>
{
{ 1, "田中" },
{ 2, "佐藤" },
{ 3, "鈴木" }
};

Console.WriteLine(users[2]); // 佐藤

Dictionaryを使うメリットは、キーで値を管理できること、検索処理を書きやすいこと、データの意味が明確になることです。

たとえば、単なるList<string>では「この名前がどのIDに対応しているのか」が分かりにくいですが、Dictionary<int, string>なら「IDと名前の対応関係」をはっきり表現できます。

2. C# Dictionaryの基本構文と宣言方法

2-1. Dictionary<TKey, TValue>の基本形

C#のDictionaryは、次のように宣言します。

C#
Dictionary<TKey, TValue> 変数名 = new Dictionary<TKey, TValue>();

TKeyにはキーの型、TValueには値の型を指定します。

たとえば、キーがint、値がstringの場合は次のようになります。

C#
Dictionary<int, string> users = new Dictionary<int, string>();

これは「整数のキーに対して文字列の値を持つDictionary」です。

実際には、C#ではvarを使って次のように書くことも多いです。

C#
var users = new Dictionary<int, string>();

varを使っても型がなくなるわけではありません。右辺のnew Dictionary<int, string>()から、コンパイラが型を推論してくれます。

2-2. using System.Collections.Genericが必要な理由

Dictionary<TKey, TValue>を使うには、通常、ファイルの先頭に次のusingを記述します。

C#
using System.Collections.Generic;

DictionarySystem.Collections.Generic名前空間に含まれているためです。

たとえば、コンソールアプリで使う場合は次のようになります。

C#
using System;
using System.Collections.Generic;

class Program
{
static void Main()
{
var users = new Dictionary<int, string>();
users.Add(1, "田中");

Console.WriteLine(users[1]);
}
}

最近のプロジェクトテンプレートでは、暗黙的なusingによって明示的に書かなくても使える場合があります。

ただし、初心者のうちはDictionaryを使うにはSystem.Collections.Genericが関係している、と覚えておくと理解しやすいです。

2-3. 文字列キー・数値キー・独自クラスキーの指定例

Dictionaryのキーには、さまざまな型を指定できます。

文字列キーを使う例です。

C#
var settings = new Dictionary<string, string>();

settings.Add("Theme", "Dark");
settings.Add("Language", "ja-JP");

Console.WriteLine(settings["Theme"]); // Dark

数値キーを使う例です。

C#
var users = new Dictionary<int, string>();

users.Add(1001, "田中");
users.Add(1002, "佐藤");

Console.WriteLine(users[1001]); // 田中

独自クラスを値として使うこともできます。

C#
class User
{
public int Id { get; set; }
public string Name { get; set; } = "";
}

var userMap = new Dictionary<int, User>();

userMap.Add(1, new User { Id = 1, Name = "田中" });

Console.WriteLine(userMap[1].Name); // 田中

また、独自クラスをキーにすることもできますが、その場合はEqualsGetHashCodeの扱いに注意が必要です。独自クラスをキーにする話は、後半で詳しく解説します。

2-4. 初期値を指定してDictionaryを作成する方法

Dictionaryは作成時に初期値を指定できます。

C#
var users = new Dictionary<int, string>
{
{ 1, "田中" },
{ 2, "佐藤" },
{ 3, "鈴木" }
};

この書き方を使うと、宣言と同時にデータを登録できます。

設定値のように、最初から決まったキーと値を持たせたい場合に便利です。

C#
var settings = new Dictionary<string, string>
{
{ "Theme", "Dark" },
{ "Language", "ja-JP" },
{ "FontSize", "14" }
};

インデクサ形式で初期化することもできます。

C#
var settings = new Dictionary<string, string>
{
["Theme"] = "Dark",
["Language"] = "ja-JP",
["FontSize"] = "14"
};

どちらもよく使われますが、インデクサ形式はキーと値の対応が見やすく、既存コードでもよく見かけます。

3. Dictionaryにデータを追加する方法

3-1. Addメソッドでキーと値を追加する

Dictionaryにデータを追加する基本的な方法は、Addメソッドを使うことです。

C#
var users = new Dictionary<int, string>();

users.Add(1, "田中");
users.Add(2, "佐藤");
users.Add(3, "鈴木");

Console.WriteLine(users[1]); // 田中

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

C#
dictionary.Add(キー, );

商品コードと価格を追加する例です。

C#
var prices = new Dictionary<string, int>();

prices.Add("A001", 1200);
prices.Add("B002", 2500);

Console.WriteLine(prices["A001"]); // 1200

Addは「新しいキーを追加する」ためのメソッドです。そのため、すでに存在するキーを追加しようとすると例外が発生します。

3-2. インデクサを使って追加する

Dictionaryでは、インデクサを使ってデータを追加することもできます。

C#
var users = new Dictionary<int, string>();

users[1] = "田中";
users[2] = "佐藤";

Console.WriteLine(users[2]); // 佐藤

インデクサとは、角括弧[]を使って値にアクセスする書き方です。

C#
dictionary[キー] = ;

存在しないキーに対してインデクサで値を代入すると、新しい要素として追加されます。

C#
var scores = new Dictionary<string, int>();

scores["Math"] = 90;
scores["English"] = 85;

Console.WriteLine(scores["Math"]); // 90

インデクサを使う書き方はシンプルなので、追加と更新をまとめて扱いたい場合によく使われます。

3-3. Addとインデクサ追加の違い

Addとインデクサは、どちらもデータを追加できますが、重複キーに対する動きが異なります。

Addの場合、すでに同じキーが存在すると例外が発生します。

C#
var users = new Dictionary<int, string>();

users.Add(1, "田中");
users.Add(1, "佐藤"); // 例外が発生

一方、インデクサの場合、すでに同じキーが存在すると値を上書きします。

C#
var users = new Dictionary<int, string>();

users[1] = "田中";
users[1] = "佐藤";

Console.WriteLine(users[1]); // 佐藤

つまり、違いは次のように整理できます。

Addは新規追加専用です。重複キーがあるとエラーにしたい場合に向いています。

インデクサは追加と更新の両方に使えます。キーがなければ追加し、キーがあれば上書きします。

「同じキーを誤って追加したら気づきたい」場合はAdd、「存在すれば更新、なければ追加」という処理にしたい場合はインデクサが便利です。

3-4. 重複キーを追加したときのエラーと対処法

Dictionaryでは、キーの重複は許されません。

次のコードは例外になります。

C#
var users = new Dictionary<int, string>();

users.Add(1, "田中");
users.Add(1, "佐藤");

このとき、次のようなエラーが発生します。

An item with the same key has already been added.

このエラーを防ぐには、事前にContainsKeyでキーが存在するか確認します。

C#
var users = new Dictionary<int, string>();

if (!users.ContainsKey(1))
{
users.Add(1, "田中");
}

また、C#ではTryAddを使って、追加できたかどうかをboolで受け取ることもできます。

C#
var users = new Dictionary<int, string>();

bool added = users.TryAdd(1, "田中");

if (added)
{
Console.WriteLine("追加しました");
}
else
{
Console.WriteLine("すでに同じキーがあります");
}

重複しても上書きしたい場合は、Addではなくインデクサを使います。

C#
users[1] = "佐藤";

4. Dictionaryから値を取得・キー検索する方法

4-1. キーを指定して値を取得する基本

Dictionaryから値を取得するには、キーを指定します。

C#
var users = new Dictionary<int, string>
{
{ 1, "田中" },
{ 2, "佐藤" }
};

string name = users[1];

Console.WriteLine(name); // 田中

users[1]は「キーが1の値を取り出す」という意味です。

文字列キーでも同じように使えます。

C#
var settings = new Dictionary<string, string>
{
{ "Theme", "Dark" },
{ "Language", "ja-JP" }
};

Console.WriteLine(settings["Language"]); // ja-JP

ただし、存在しないキーを指定すると例外が発生するため注意が必要です。

4-2. ContainsKeyでキーの存在を確認する

キーが存在するか確認するには、ContainsKeyを使います。

C#
var users = new Dictionary<int, string>
{
{ 1, "田中" },
{ 2, "佐藤" }
};

if (users.ContainsKey(1))
{
Console.WriteLine(users[1]);
}
else
{
Console.WriteLine("指定されたキーは存在しません");
}

ContainsKeyは、指定したキーが存在すればtrue、存在しなければfalseを返します。

C#
bool exists = users.ContainsKey(1);

存在しないキーを参照する前にContainsKeyで確認すれば、KeyNotFoundExceptionを防げます。

ただし、ContainsKeyで確認したあとに再度インデクサで取得するため、検索処理が二度発生する場合があります。

そのため、値の取得まで行いたい場合は、次に紹介するTryGetValueがよく使われます。

4-3. TryGetValueで安全に値を取得する

TryGetValueは、キーの存在確認と値の取得を同時に行える便利なメソッドです。

C#
var users = new Dictionary<int, string>
{
{ 1, "田中" },
{ 2, "佐藤" }
};

if (users.TryGetValue(1, out string name))
{
Console.WriteLine(name);
}
else
{
Console.WriteLine("指定されたキーは存在しません");
}

TryGetValueは、キーが存在すればtrueを返し、対応する値をout変数に入れます。

キーが存在しなければfalseを返します。

C#
if (dictionary.TryGetValue(キー, out 値を受け取る変数))
{
// 見つかった場合
}
else
{
// 見つからなかった場合
}

Dictionaryから安全に値を取得したい場合は、TryGetValueを使うのが定番です。

特に、外部入力やユーザー入力をもとにキー検索する場合は、存在しないキーが指定される可能性があるため、TryGetValueを使うと安心です。

4-4. 存在しないキーを参照したときの例外に注意

存在しないキーをインデクサで参照すると、KeyNotFoundExceptionが発生します。

C#
var users = new Dictionary<int, string>
{
{ 1, "田中" }
};

Console.WriteLine(users[99]); // KeyNotFoundException

これは「キー99がDictionary内に存在しない」という意味です。

このような例外を防ぐには、ContainsKeyまたはTryGetValueを使います。

C#
if (users.TryGetValue(99, out string name))
{
Console.WriteLine(name);
}
else
{
Console.WriteLine("ユーザーが見つかりません");
}

初心者のうちは、dictionary[key]で直接取得するときは「そのキーが必ず存在する場合だけ」と覚えておくとよいでしょう。

存在するか分からないキーを扱う場合は、TryGetValueを使うのがおすすめです。

4-5. 値からキーを検索したい場合の考え方

Dictionaryは、基本的に「キーから値を探す」ためのコレクションです。

そのため、値からキーを検索する処理は得意ではありません。

たとえば、次のようなDictionaryがあるとします。

C#
var users = new Dictionary<int, string>
{
{ 1, "田中" },
{ 2, "佐藤" },
{ 3, "鈴木" }
};

名前からIDを探したい場合は、foreachで探せます。

C#
foreach (var pair in users)
{
if (pair.Value == "佐藤")
{
Console.WriteLine(pair.Key); // 2
}
}

LINQを使う方法もあります。

C#
var result = users.FirstOrDefault(pair => pair.Value == "佐藤");

Console.WriteLine(result.Key);

ただし、値が重複している可能性がある場合は注意が必要です。

C#
var users = new Dictionary<int, string>
{
{ 1, "田中" },
{ 2, "佐藤" },
{ 3, "佐藤" }
};

この場合、「佐藤」に対応するキーは23の両方です。

値から頻繁に検索したい場合は、設計を見直して、逆引き用のDictionaryを別に用意することも検討しましょう。

C#
var userIdsByName = new Dictionary<string, int>
{
{ "田中", 1 },
{ "佐藤", 2 }
};

5. Dictionaryの値を更新・上書きする方法

5-1. インデクサを使って値を更新する

Dictionaryの値を更新するには、インデクサを使います。

C#
var users = new Dictionary<int, string>
{
{ 1, "田中" },
{ 2, "佐藤" }
};

users[1] = "田中太郎";

Console.WriteLine(users[1]); // 田中太郎

すでに存在するキーに対して値を代入すると、値が上書きされます。

商品価格を更新する例です。

C#
var prices = new Dictionary<string, int>
{
{ "A001", 1200 },
{ "B002", 2500 }
};

prices["A001"] = 1300;

Console.WriteLine(prices["A001"]); // 1300

Dictionaryでは、キーは同じまま、値だけを変更できます。

5-2. 追加と更新を同時に扱う書き方

インデクサを使うと、追加と更新を同じ書き方で扱えます。

C#
var users = new Dictionary<int, string>();

users[1] = "田中"; // 追加
users[1] = "田中太郎"; // 更新

Console.WriteLine(users[1]); // 田中太郎

キーが存在しない場合は追加され、キーが存在する場合は更新されます。

この動きは、設定値の登録やキャッシュの更新などで便利です。

C#
var settings = new Dictionary<string, string>();

settings["Theme"] = "Light";
settings["Theme"] = "Dark";

Console.WriteLine(settings["Theme"]); // Dark

「同じキーがあれば上書きしてよい」という処理では、インデクサを使うとコードが簡潔になります。

5-3. 既存キーだけ更新したい場合の実装例

「キーが存在する場合だけ更新し、存在しない場合は何もしない」という処理にしたい場合は、ContainsKeyを使います。

C#
var users = new Dictionary<int, string>
{
{ 1, "田中" },
{ 2, "佐藤" }
};

if (users.ContainsKey(1))
{
users[1] = "田中太郎";
}

存在しないキーは追加したくない場合に有効です。

C#
if (users.ContainsKey(99))
{
users[99] = "新しいユーザー";
}
else
{
Console.WriteLine("指定されたユーザーIDは存在しません");
}

このようにすれば、誤って新しいキーを追加してしまうことを防げます。

値を取得してから更新したい場合は、TryGetValueも使えます。

C#
if (users.TryGetValue(1, out string currentName))
{
users[1] = currentName + "さん";
}

5-4. TryAddを使った重複防止の方法

TryAddは、キーが存在しない場合だけ追加するメソッドです。

C#
var users = new Dictionary<int, string>();

bool added = users.TryAdd(1, "田中");

Console.WriteLine(added); // True

同じキーをもう一度追加しようとすると、追加されずにfalseが返ります。

C#
bool addedAgain = users.TryAdd(1, "佐藤");

Console.WriteLine(addedAgain); // False
Console.WriteLine(users[1]); // 田中

TryAddを使うと、例外を発生させずに重複キーを防げます。

C#
if (!users.TryAdd(1, "佐藤"))
{
Console.WriteLine("すでに同じキーが存在します");
}

「既存の値を上書きしたくない」「重複時は何もしない」という場合は、AddよりもTryAddの方が扱いやすいことがあります。

6. Dictionaryの削除・件数確認・ループ処理

6-1. Removeで指定キーのデータを削除する

Dictionaryからデータを削除するには、Removeを使います。

C#
var users = new Dictionary<int, string>
{
{ 1, "田中" },
{ 2, "佐藤" },
{ 3, "鈴木" }
};

users.Remove(2);

Console.WriteLine(users.ContainsKey(2)); // False

Removeには削除したいキーを指定します。

C#
dictionary.Remove(キー);

Removeは、削除できた場合にtrue、指定したキーが存在しなかった場合にfalseを返します。

C#
bool removed = users.Remove(2);

if (removed)
{
Console.WriteLine("削除しました");
}
else
{
Console.WriteLine("指定されたキーは存在しません");
}

存在しないキーを指定しても例外にはならないため、安全に使えます。

6-2. Clearですべてのデータを削除する

Dictionaryの中身をすべて削除したい場合は、Clearを使います。

C#
var users = new Dictionary<int, string>
{
{ 1, "田中" },
{ 2, "佐藤" }
};

users.Clear();

Console.WriteLine(users.Count); // 0

Clearを呼び出すと、すべてのキーと値が削除されます。

C#
dictionary.Clear();

一時的に使ったデータをリセットしたい場合や、再読み込み前に中身を空にしたい場合に便利です。

6-3. Countで要素数を確認する

Dictionaryに登録されている要素数を確認するには、Countプロパティを使います。

C#
var users = new Dictionary<int, string>
{
{ 1, "田中" },
{ 2, "佐藤" },
{ 3, "鈴木" }
};

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

Countは、キーと値のペアの数を返します。

空かどうかを確認する場合にも使えます。

C#
if (users.Count == 0)
{
Console.WriteLine("データがありません");
}
else
{
Console.WriteLine("データがあります");
}

6-4. foreachでキーと値を順番に取り出す

Dictionaryのすべての要素を処理するには、foreachを使います。

C#
var users = new Dictionary<int, string>
{
{ 1, "田中" },
{ 2, "佐藤" },
{ 3, "鈴木" }
};

foreach (var pair in users)
{
Console.WriteLine($"{pair.Key}: {pair.Value}");
}

pair.Keyでキー、pair.Valueで値を取得できます。

出力例です。

1: 田中
2: 佐藤
3: 鈴木

型を明示して書く場合は、次のようになります。

C#
foreach (KeyValuePair<int, string> pair in users)
{
Console.WriteLine(pair.Key);
Console.WriteLine(pair.Value);
}

普段はvarを使うことが多いですが、Dictionaryの要素はKeyValuePair<TKey, TValue>として取り出されることを理解しておくと便利です。

6-5. KeysとValuesを使って一覧を取得する

キーだけを取り出したい場合はKeysを使います。

C#
var users = new Dictionary<int, string>
{
{ 1, "田中" },
{ 2, "佐藤" },
{ 3, "鈴木" }
};

foreach (var key in users.Keys)
{
Console.WriteLine(key);
}

値だけを取り出したい場合はValuesを使います。

C#
foreach (var value in users.Values)
{
Console.WriteLine(value);
}

KeysValuesを使えば、キー一覧や値一覧を簡単に処理できます。

ただし、キーと値の対応関係を同時に扱いたい場合は、通常のforeachKeyValuePairを取り出す方が分かりやすいです。

C#
foreach (var pair in users)
{
Console.WriteLine($"{pair.Key} = {pair.Value}");
}

7. Dictionaryを使った実践サンプルコード

7-1. ユーザーIDとユーザー名を管理する例

ユーザーIDとユーザー名を管理する例です。

C#
using System;
using System.Collections.Generic;

class Program
{
static void Main()
{
var users = new Dictionary<int, string>
{
{ 1001, "田中" },
{ 1002, "佐藤" },
{ 1003, "鈴木" }
};

int targetId = 1002;

if (users.TryGetValue(targetId, out string name))
{
Console.WriteLine($"ユーザー名: {name}");
}
else
{
Console.WriteLine("ユーザーが見つかりません");
}
}
}

この例では、ユーザーIDをキー、ユーザー名を値として管理しています。

TryGetValueを使っているため、存在しないユーザーIDを指定しても例外になりません。

ユーザー管理では、IDをもとに名前や情報を取得する処理がよくあります。そのような場面でDictionary<int, string>はシンプルに使えます。

7-2. 商品コードと価格を管理する例

商品コードと価格を管理する例です。

C#
var prices = new Dictionary<string, int>
{
{ "A001", 1200 },
{ "B002", 2500 },
{ "C003", 980 }
};

string productCode = "B002";

if (prices.TryGetValue(productCode, out int price))
{
Console.WriteLine($"価格: {price}円");
}
else
{
Console.WriteLine("商品コードが見つかりません");
}

商品コードのように、文字列で一意に識別できる値はDictionaryのキーに向いています。

価格を更新したい場合は、インデクサを使えます。

C#
prices["B002"] = 2300;

新商品を追加したい場合は、Addまたはインデクサを使います。

C#
prices.Add("D004", 1500);

すでに商品コードが存在する可能性がある場合は、TryAddを使うと安全です。

C#
if (!prices.TryAdd("A001", 999))
{
Console.WriteLine("すでに登録済みの商品コードです");
}

7-3. 設定値をキーと値で管理する例

アプリケーションの設定値を管理する場合にも、Dictionaryは便利です。

C#
var settings = new Dictionary<string, string>
{
{ "Theme", "Dark" },
{ "Language", "ja-JP" },
{ "LogLevel", "Debug" }
};

Console.WriteLine(settings["Theme"]); // Dark
Console.WriteLine(settings["Language"]); // ja-JP

設定値は「設定名」と「設定内容」のペアで管理されることが多いため、Dictionary<string, string>と相性がよいです。

存在しない設定キーに備えるなら、TryGetValueを使います。

C#
if (settings.TryGetValue("LogLevel", out string logLevel))
{
Console.WriteLine($"ログレベル: {logLevel}");
}
else
{
Console.WriteLine("ログレベルは未設定です");
}

初期値を返したい場合は、次のように書けます。

C#
string theme = settings.TryGetValue("Theme", out string value)
? value
: "Light";

Console.WriteLine(theme);

7-4. nullや空文字を扱うときの注意点

Dictionaryでは、値にnullを入れられる場合があります。

C#
var users = new Dictionary<int, string?>();

users[1] = null;

一方、キーにnullを指定することはできません。

C#
var map = new Dictionary<string, int>();

// map.Add(null, 1); // 例外の原因になる

文字列キーを使う場合、空文字""はキーとして使えます。

C#
var map = new Dictionary<string, string>();

map[""] = "空文字キー";

Console.WriteLine(map[""]); // 空文字キー

ただし、空文字キーはコードの意図が分かりにくくなることがあります。

そのため、実務ではキーがnullや空文字にならないようにチェックすることが多いです。

C#
string key = "";

if (!string.IsNullOrEmpty(key))
{
map[key] = "値";
}
else
{
Console.WriteLine("キーが空です");
}

特に外部入力をキーにする場合は、null、空文字、前後の空白、大文字小文字の違いに注意しましょう。

8. Dictionaryでよくあるエラーと解決方法

8-1. An item with the same key has already been addedの原因

An item with the same key has already been addedは、同じキーをAddで追加しようとしたときに発生するエラーです。

C#
var users = new Dictionary<int, string>();

users.Add(1, "田中");
users.Add(1, "佐藤"); // エラー

Dictionaryでは、同じキーを複数登録することはできません。

解決方法は、目的によって変わります。

重複している場合に追加しないなら、ContainsKeyを使います。

C#
if (!users.ContainsKey(1))
{
users.Add(1, "佐藤");
}

重複している場合に上書きしたいなら、インデクサを使います。

C#
users[1] = "佐藤";

重複している場合に追加失敗として扱いたいなら、TryAddを使います。

C#
if (!users.TryAdd(1, "佐藤"))
{
Console.WriteLine("すでに同じキーがあります");
}

8-2. KeyNotFoundExceptionの原因

KeyNotFoundExceptionは、存在しないキーをインデクサで取得しようとしたときに発生します。

C#
var users = new Dictionary<int, string>
{
{ 1, "田中" }
};

Console.WriteLine(users[2]); // KeyNotFoundException

このエラーを防ぐには、TryGetValueを使います。

C#
if (users.TryGetValue(2, out string name))
{
Console.WriteLine(name);
}
else
{
Console.WriteLine("キーが存在しません");
}

または、ContainsKeyで確認してから取得します。

C#
if (users.ContainsKey(2))
{
Console.WriteLine(users[2]);
}

存在することが保証されていないキーを扱う場合は、直接dictionary[key]で取得しないようにしましょう。

8-3. 型が一致しないときのコンパイルエラー

Dictionary<TKey, TValue>では、キーと値の型が決まっています。

たとえば、次のDictionaryはキーがint、値がstringです。

C#
var users = new Dictionary<int, string>();

そのため、キーに文字列を指定するとコンパイルエラーになります。

C#
// users.Add("1", "田中"); // エラー

また、値に数値を指定してもエラーになります。

C#
// users.Add(1, 100); // エラー

正しくは、キーにint、値にstringを指定します。

C#
users.Add(1, "田中");

型が合わない場合は、Dictionaryの宣言を見直しましょう。

商品コードのようにキーが文字列なら、次のようにします。

C#
var prices = new Dictionary<string, int>();

ユーザーIDのようにキーが数値なら、次のようにします。

C#
var usersById = new Dictionary<int, string>();

8-4. キーの大文字・小文字違いによる検索ミス

文字列キーでは、大文字と小文字の違いに注意が必要です。

通常、Dictionary<string, TValue>では、"User""user"は別のキーとして扱われます。

C#
var map = new Dictionary<string, string>();

map["User"] = "田中";

Console.WriteLine(map.ContainsKey("user")); // False

大文字小文字を区別せずに扱いたい場合は、StringComparer.OrdinalIgnoreCaseを指定します。

C#
var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

map["User"] = "田中";

Console.WriteLine(map.ContainsKey("user")); // True
Console.WriteLine(map["USER"]); // 田中

設定キーやコマンド名など、大文字小文字の違いを無視したい場合に便利です。

ただし、ユーザー名やコードなど、大文字小文字を区別すべきデータでは使わない方がよい場合もあります。

8-5. Dictionaryの順序に依存してはいけない理由

Dictionaryは、キーを使って値を取得するためのコレクションです。

そのため、基本的には順序に依存した処理を書くべきではありません。

C#
var map = new Dictionary<int, string>
{
{ 3, "鈴木" },
{ 1, "田中" },
{ 2, "佐藤" }
};

foreach (var pair in map)
{
Console.WriteLine(pair.Value);
}

環境や実装によって、列挙順が挿入順に見えることがあります。

しかし、Dictionaryは「順序を表現するためのコレクション」として使うものではありません。

順番が重要な場合は、ListSortedDictionarySortedListなどを検討しましょう。

キーで高速に検索したいならDictionary、順番を明確に扱いたいなら目的に合った別のコレクションを選ぶことが大切です。

9. Dictionaryを使いこなす応用テクニック

9-1. varを使って簡潔に宣言する

Dictionaryは型名が長くなりやすいため、varを使うとコードがすっきりします。

C#
Dictionary<int, string> users = new Dictionary<int, string>();

これは次のように書けます。

C#
var users = new Dictionary<int, string>();

初期値を指定する場合も、varを使えます。

C#
var users = new Dictionary<int, string>
{
{ 1, "田中" },
{ 2, "佐藤" }
};

varは型を曖昧にするものではありません。右辺から型が明確に分かる場合に、コンパイラが型を推論してくれる仕組みです。

コードの可読性を保ちながら、冗長な記述を減らせます。

9-2. LINQと組み合わせて検索・絞り込みする

DictionaryはLINQと組み合わせて使うこともできます。

たとえば、価格が1000円以上の商品だけを取り出す例です。

C#
using System.Linq;

var prices = new Dictionary<string, int>
{
{ "A001", 800 },
{ "B002", 1200 },
{ "C003", 2500 }
};

var expensiveItems = prices.Where(pair => pair.Value >= 1000);

foreach (var item in expensiveItems)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}

キーで絞り込むこともできます。

C#
var items = prices.Where(pair => pair.Key.StartsWith("A"));

値の最大値を取得する例です。

C#
int maxPrice = prices.Values.Max();

Console.WriteLine(maxPrice); // 2500

ただし、LINQを使った検索は、基本的には全要素を順番に見て処理します。

キーで直接検索できる場合は、TryGetValueやインデクサを使う方が自然です。

9-3. ToDictionaryでListからDictionaryへ変換する

ListからDictionaryへ変換したい場合は、LINQのToDictionaryを使えます。

C#
using System.Collections.Generic;
using System.Linq;

class User
{
public int Id { get; set; }
public string Name { get; set; } = "";
}

var users = new List<User>
{
new User { Id = 1, Name = "田中" },
new User { Id = 2, Name = "佐藤" },
new User { Id = 3, Name = "鈴木" }
};

var userMap = users.ToDictionary(user => user.Id, user => user.Name);

Console.WriteLine(userMap[2]); // 佐藤

ToDictionaryの第一引数にはキーにする値、第二引数には値にするデータを指定します。

C#
var dictionary = list.ToDictionary(x => x.Id, x => x.Name);

オブジェクト自体を値として持たせることもできます。

C#
var userMap = users.ToDictionary(user => user.Id);

Console.WriteLine(userMap[1].Name);

ただし、キーが重複していると例外が発生します。

C#
var userMap = users.ToDictionary(user => user.Id);

Idが重複する可能性がある場合は、事前に重複を除く、またはGroupByを使うなどの工夫が必要です。

9-4. Dictionaryの中にListを持たせる方法

1つのキーに対して複数の値を持たせたい場合は、Dictionary<TKey, List<TValue>>を使います。

たとえば、部署ごとに社員名を管理する例です。

C#
var membersByDepartment = new Dictionary<string, List<string>>();

membersByDepartment["営業部"] = new List<string> { "田中", "佐藤" };
membersByDepartment["開発部"] = new List<string> { "鈴木", "高橋" };

foreach (var name in membersByDepartment["営業部"])
{
Console.WriteLine(name);
}

あとからリストに追加する場合は、キーが存在するか確認します。

C#
string department = "営業部";
string member = "山田";

if (!membersByDepartment.ContainsKey(department))
{
membersByDepartment[department] = new List<string>();
}

membersByDepartment[department].Add(member);

TryGetValueを使うと、より効率的に書けます。

C#
if (!membersByDepartment.TryGetValue(department, out var members))
{
members = new List<string>();
membersByDepartment[department] = members;
}

members.Add(member);

このように、Dictionaryの値としてListを持たせると、「カテゴリごとの一覧」「グループごとのデータ」を表現できます。

9-5. 独自クラスをキーにするときの注意点

独自クラスをDictionaryのキーにする場合は、等価比較に注意が必要です。

たとえば、次のようなクラスをキーにするとします。

C#
class ProductKey
{
public string Code { get; set; } = "";
}

次のコードでは、同じCodeを持っていても、別インスタンスとして扱われることがあります。

C#
var map = new Dictionary<ProductKey, string>();

var key1 = new ProductKey { Code = "A001" };
var key2 = new ProductKey { Code = "A001" };

map[key1] = "商品A";

Console.WriteLine(map.ContainsKey(key2)); // 期待通りにならない場合がある

独自クラスをキーとして使う場合は、EqualsGetHashCodeを適切に実装する必要があります。

C#
class ProductKey
{
public string Code { get; set; } = "";

public override bool Equals(object? obj)
{
return obj is ProductKey other && Code == other.Code;
}

public override int GetHashCode()
{
return Code.GetHashCode();
}
}

C#では、recordを使うと値の比較を簡潔に扱えます。

C#
record ProductKey(string Code);

var map = new Dictionary<ProductKey, string>();

map[new ProductKey("A001")] = "商品A";

Console.WriteLine(map[new ProductKey("A001")]); // 商品A

独自クラスをキーにする場合は、「同じキーとみなす条件」を明確にすることが重要です。

10. C# Dictionaryと関連コレクションの違い

10-1. DictionaryとHashtableの違い

Hashtableもキーと値のペアを管理できるコレクションです。

C#
using System.Collections;

var table = new Hashtable();

table["A001"] = 1200;
table["B002"] = 2500;

ただし、Hashtableは古い非ジェネリックなコレクションです。

キーや値の型をコンパイル時に厳密にチェックしにくいため、現在のC#では基本的にDictionary<TKey, TValue>を使うのが一般的です。

C#
var prices = new Dictionary<string, int>();

prices["A001"] = 1200;

Dictionary<string, int>なら、キーがstring、値がintであることが明確です。

型安全に扱えるため、不要なキャストを減らせます。

C#
int price = prices["A001"];

新しくコードを書く場合は、特別な理由がない限りHashtableではなくDictionaryを選ぶとよいでしょう。

10-2. DictionaryとSortedDictionaryの違い

SortedDictionary<TKey, TValue>は、キーの順序に基づいて要素を管理するコレクションです。

C#
var map = new SortedDictionary<int, string>
{
{ 3, "鈴木" },
{ 1, "田中" },
{ 2, "佐藤" }
};

foreach (var pair in map)
{
Console.WriteLine($"{pair.Key}: {pair.Value}");
}

この場合、キーの昇順で取り出されます。

1: 田中
2: 佐藤
3: 鈴木

Dictionaryはキーによる高速な検索に向いていますが、順序を目的としたコレクションではありません。

一方、SortedDictionaryはキー順で管理したい場合に便利です。

ただし、単純にキーで値を取得するだけなら、通常はDictionaryで十分です。

10-3. DictionaryとConcurrentDictionaryの違い

ConcurrentDictionary<TKey, TValue>は、複数のスレッドから同時にアクセスされる場面を想定したコレクションです。

C#
using System.Collections.Concurrent;

var map = new ConcurrentDictionary<int, string>();

map.TryAdd(1, "田中");
map.TryUpdate(1, "田中太郎", "田中");

通常のDictionaryは、複数スレッドから同時に書き込みが発生するようなケースでは注意が必要です。

Webアプリケーションや並列処理で、複数の処理が同時に同じコレクションを更新する場合は、ConcurrentDictionaryを検討します。

たとえば、キャッシュやカウント情報を複数スレッドで共有する場合に使われます。

C#
var cache = new ConcurrentDictionary<string, string>();

string value = cache.GetOrAdd("key1", "default value");

通常の単一スレッド処理や、メソッド内だけで使う一時的なデータ管理であれば、Dictionaryで問題ありません。

10-4. 用途別にどれを選ぶべきか

基本的には、キーと値のペアを扱いたいならDictionary<TKey, TValue>を選べば問題ありません。

型安全にキーと値を管理したい場合は、Dictionaryが最初の選択肢です。

キーの順番で取り出したい場合は、SortedDictionaryを検討します。

複数スレッドから同時に更新する可能性がある場合は、ConcurrentDictionaryを検討します。

古いコードとの互換性など特別な理由がある場合を除き、Hashtableを新規コードで積極的に使う場面は多くありません。

まとめると、通常のMap用途ならDictionary、キー順が必要ならSortedDictionary、並行処理が必要ならConcurrentDictionaryです。

11. C# Dictionaryのよくある質問

11-1. C#でMapを使いたい場合は何を使えばいい?

C#でJavaのMapのようなものを使いたい場合は、Dictionary<TKey, TValue>を使います。

C#
var map = new Dictionary<int, string>();

map.Add(1, "田中");
map.Add(2, "佐藤");

キーと値のペアを管理し、キーを使って値を取得できます。

C#
Console.WriteLine(map[1]); // 田中

JavaのHashMapに近い感覚で使えるため、C#でMapを探している場合は、まずDictionaryを覚えるとよいでしょう。

11-2. Dictionaryのキーに重複は許される?

Dictionaryのキーに重複は許されません。

次のように同じキーをAddで追加するとエラーになります。

C#
var map = new Dictionary<int, string>();

map.Add(1, "田中");
map.Add(1, "佐藤"); // エラー

同じキーに対して値を更新したい場合は、インデクサを使います。

C#
map[1] = "佐藤";

重複を避けて追加したい場合は、TryAddを使います。

C#
if (!map.TryAdd(1, "佐藤"))
{
Console.WriteLine("すでにキーが存在します");
}

11-3. Dictionaryの値に重複は許される?

Dictionaryでは、値の重複は許されます。

C#
var users = new Dictionary<int, string>
{
{ 1, "佐藤" },
{ 2, "佐藤" },
{ 3, "田中" }
};

この例では、キー1とキー2の値がどちらも"佐藤"です。

Dictionaryで一意でなければならないのはキーです。

値は重複していても問題ありません。

ただし、値からキーを逆引きしたい場合は、値が重複していると複数のキーが該当する可能性があります。

11-4. Dictionaryは順番を保持する?

Dictionaryは、順番を表現するためのコレクションではありません。

そのため、要素の順番に依存した処理を書くのは避けるべきです。

C#
foreach (var pair in map)
{
Console.WriteLine(pair.Key);
}

このようなループで取り出される順番が、常に期待通りになるとは考えない方が安全です。

順番が重要な場合は、Listで順序を管理する、またはSortedDictionaryでキー順に管理するなど、目的に合ったコレクションを使いましょう。

11-5. Dictionaryでキーが存在しない場合はどう処理する?

キーが存在しない可能性がある場合は、TryGetValueを使うのがおすすめです。

C#
var map = new Dictionary<int, string>
{
{ 1, "田中" }
};

if (map.TryGetValue(2, out string value))
{
Console.WriteLine(value);
}
else
{
Console.WriteLine("キーが存在しません");
}

ContainsKeyを使う方法もあります。

C#
if (map.ContainsKey(2))
{
Console.WriteLine(map[2]);
}

ただし、値の取得まで行うならTryGetValueの方が効率的で、C#ではよく使われる書き方です。

存在するか分からないキーに対して、いきなりmap[key]でアクセスするとKeyNotFoundExceptionになる可能性があるため注意しましょう。

まとめ

C#にはJavaのMapという名前のコレクションはありませんが、同じような用途にはDictionary<TKey, TValue>を使います。

Dictionaryは、キーと値をペアで管理し、キーを指定して値をすばやく取得できる便利なコレクションです。

基本的な宣言は次のように書きます。

C#
var map = new Dictionary<int, string>();

データの追加にはAddまたはインデクサを使います。

C#
map.Add(1, "田中");
map[2] = "佐藤";

値を取得するときは、キーが必ず存在するならインデクサを使えます。

C#
Console.WriteLine(map[1]);

キーが存在するか分からない場合は、TryGetValueを使うと安全です。

C#
if (map.TryGetValue(1, out string value))
{
Console.WriteLine(value);
}

値の更新はインデクサで行えます。

C#
map[1] = "田中太郎";

削除はRemove、全削除はClear、件数確認はCount、全件処理はforeachを使います。

C#
map.Remove(1);
map.Clear();
Console.WriteLine(map.Count);

C#でMapのようなデータ構造を使いたい場合は、まずDictionaryを理解することが重要です。

ユーザーIDと名前、商品コードと価格、設定キーと設定値など、キーと値の対応関係を扱う場面では、Dictionaryを使うことでコードを分かりやすく、扱いやすくできます。