C#のHashMapとは?Dictionaryでの使い方・追加/取得/削除・Javaとの違いを初心者向けに解説

はじめに

C#で「HashMapを使いたい」と調べると、多くの場合はDictionary<TKey, TValue>を使うことになります。

特にJava経験者の場合、HashMapという名前に慣れているため、C#でも同じクラスがあると思うかもしれません。しかし、C#の標準的なコレクションでは、JavaのHashMapに近い役割を持つのはDictionary<TKey, TValue>です。

この記事では、C#におけるHashMap相当の仕組みとしてDictionaryを中心に、追加・取得・更新・削除の基本操作、JavaのHashMapとの違い、Hashtableとの違い、実践的なサンプルコードまで初心者向けに解説します。

csharp hashmapについて調べている方は、まず「C#ではHashMapではなくDictionaryを使う」と覚えておけば大丈夫です。

1. C#のHashMapとは?まず結論から理解しよう

1-1. C#に「HashMap」というクラスは基本的に存在しない

C#でHashMapを使いたい場合、まず知っておきたいのは、C#の標準ライブラリにはJavaのようなHashMapという名前のクラスは基本的に存在しないという点です。

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

Java
HashMap<String, Integer> map = new HashMap<>();

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

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

つまり、JavaのHashMapに近いものをC#で使いたい場合は、Dictionary<TKey, TValue>を使います。

C#で「HashMap」と検索している場合、多くのケースではDictionaryの使い方を理解すれば目的を達成できます。

1-2. C#ではHashMapの代わりにDictionary<TKey, TValue>を使う

C#のDictionary<TKey, TValue>は、キーと値のペアでデータを管理するコレクションです。

例えば、ユーザーIDからユーザー名を取得したい場合、次のように書けます。

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

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

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

この例では、12がキー、"田中""佐藤"が値です。

Dictionaryを使うと、キーを指定して目的の値をすばやく取得できます。配列やListのように先頭から順番に探す必要がないため、検索処理に向いています。

1-3. HashMap・Dictionary・Map・Hashtableの違いを初心者向けに整理

似た言葉が多いので、まず簡単に整理しておきましょう。

用語主な意味
HashMapJavaでよく使われるキーと値のコレクション
DictionaryC#でHashMap相当として使われるコレクション
Mapキーと値を対応付けるデータ構造の一般的な呼び方
HashtableC#にもある古いコレクション。現在はDictionaryがよく使われる

初心者のうちは、次の理解で十分です。

C#ではHashMapではなくDictionaryを使う。
JavaではHashMapを使う。
Mapはキーと値を扱う仕組みの一般的な名前。
Hashtableは古い書き方で、通常はDictionaryを選ぶ。

1-4. 「キー」と「値」のペアでデータを管理する仕組み

Dictionaryは、キーと値を1セットにして管理します。

例えば、次のようなデータがあるとします。

キー
appleりんご
bananaバナナ
orangeオレンジ

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

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

fruits.Add("apple", "りんご");
fruits.Add("banana", "バナナ");
fruits.Add("orange", "オレンジ");

キーを指定すれば、対応する値を取得できます。

C#
Console.WriteLine(fruits["apple"]); // りんご

このように、「ある値に名前やIDを付けて管理したい」ときにDictionaryは非常に便利です。

2. C#のDictionaryの基本構文と使いどころ

2-1. Dictionary<TKey, TValue>の書き方

C#のDictionaryは、次の形式で書きます。

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

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

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

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

この場合、次のようなデータを管理できます。

C#
scores.Add("田中", 80);
scores.Add("佐藤", 95);

"田中""佐藤"がキー、8095が値です。

2-2. string・intなどキーと値の型を指定する方法

Dictionaryでは、キーと値の型を自由に組み合わせられます。

例えば、キーがint、値がstringの場合です。

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

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

キーがstring、値がdecimalの場合です。

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

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

キーがstring、値がboolの場合です。

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

settings.Add("DarkMode", true);
settings.Add("Notification", false);

このように、Dictionary<TKey, TValue>では、扱いたいデータに合わせて型を指定します。

2-3. varを使ったDictionaryの宣言方法

C#ではvarを使って型の記述を短くできます。

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

これは次のコードと同じ意味です。

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

varを使っても、C#が右辺から型を推論してくれます。
ただし、varは「何でも入れられる型」ではありません。

次の例では、scoresDictionary<string, int>として扱われます。

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

scores.Add("田中", 80);
scores.Add("佐藤", 95);

初心者のうちは、型を明確にしたい場合はDictionary<string, int>のように書き、慣れてきたらvarを使うとよいでしょう。

2-4. Dictionaryが向いているケース

Dictionaryは、キーを使って値をすばやく取り出したいときに向いています。

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

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

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

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

ユーザーID、商品コード、設定名、単語など、重複しないキーでデータを管理したい場合に便利です。

具体的には、次のような用途があります。

用途キー
ユーザー管理ユーザーIDユーザー名
商品管理商品コード価格
設定管理設定名設定値
集計処理単語出現回数

キーから値を取得する処理が多い場合は、Dictionaryを検討するとよいでしょう。

2-5. Listや配列との使い分け

DictionaryList、配列は似ているようで用途が異なります。

Listや配列は、基本的に順番でデータを管理します。

C#
List<string> names = new List<string>();

names.Add("田中");
names.Add("佐藤");
names.Add("鈴木");

Console.WriteLine(names[0]); // 田中

一方、Dictionaryはキーでデータを管理します。

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

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

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

使い分けの目安は次のとおりです。

使いたいこと向いている型
順番にデータを並べたいList・配列
先頭から順に処理したいList・配列
IDや名前から値を取得したいDictionary
キーと値のペアで管理したいDictionary

「何番目のデータか」で扱うならListや配列、
「どのキーのデータか」で扱うならDictionaryが向いています。

3. C# Dictionaryの追加・取得・更新・削除の基本操作

3-1. Addメソッドで要素を追加する

Dictionaryに要素を追加するには、Addメソッドを使います。

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

scores.Add("田中", 80);
scores.Add("佐藤", 95);
scores.Add("鈴木", 70);

Addメソッドの第1引数がキー、第2引数が値です。

C#
scores.Add("キー", );

例えば、商品コードと価格を追加する場合は次のようになります。

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

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

ただし、同じキーをAddで追加しようとすると例外が発生します。

C#
scores.Add("田中", 80);
scores.Add("田中", 90); // 例外が発生する

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

3-2. インデクサで要素を追加・更新する

Dictionaryでは、インデクサを使って要素を追加または更新できます。

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

scores["田中"] = 80;
scores["佐藤"] = 95;

この書き方では、キーが存在しない場合は新しく追加されます。

C#
scores["鈴木"] = 70;

すでにキーが存在する場合は、値が更新されます。

C#
scores["田中"] = 90;

つまり、次のような違いがあります。

書き方キーが存在しない場合キーが存在する場合
Add追加する例外が発生する
インデクサ追加する更新する

新規追加だけを明確にしたい場合はAdd、追加または更新をしたい場合はインデクサを使うとよいでしょう。

3-3. キーを指定して値を取得する

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

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

scores.Add("田中", 80);
scores.Add("佐藤", 95);

int tanakaScore = scores["田中"];

Console.WriteLine(tanakaScore); // 80

このように、dictionary[キー]の形で値を取り出せます。

C#
Console.WriteLine(scores["佐藤"]); // 95

ただし、存在しないキーを指定すると例外が発生します。

C#
Console.WriteLine(scores["山田"]); // 例外が発生する

安全に取得したい場合は、次に紹介するTryGetValueを使います。

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

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

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

scores.Add("田中", 80);
scores.Add("佐藤", 95);

if (scores.TryGetValue("田中", out int score))
{
Console.WriteLine(score);
}
else
{
Console.WriteLine("キーが存在しません");
}

TryGetValueは、キーが存在すればtrueを返し、値をout引数に入れます。
キーが存在しなければfalseを返します。

存在しないキーを指定しても例外が発生しないため、実務でもよく使われる書き方です。

C#
if (scores.TryGetValue("山田", out int yamadaScore))
{
Console.WriteLine(yamadaScore);
}
else
{
Console.WriteLine("山田さんの点数は登録されていません");
}

Dictionaryから値を取得するときは、インデクサよりもTryGetValueを使う方が安全な場面が多いです。

3-5. ContainsKeyでキーの存在を確認する

キーが存在するかどうかを確認したい場合は、ContainsKeyを使います。

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

scores.Add("田中", 80);
scores.Add("佐藤", 95);

if (scores.ContainsKey("田中"))
{
Console.WriteLine(scores["田中"]);
}

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

C#
if (!scores.ContainsKey("山田"))
{
Console.WriteLine("山田さんは登録されていません");
}

ただし、値を取得する目的でContainsKeyを使ったあとにインデクサで取得すると、同じキーを2回探すことになります。

C#
if (scores.ContainsKey("田中"))
{
Console.WriteLine(scores["田中"]);
}

値も必要な場合は、TryGetValueの方が効率的で書き方もきれいです。

C#
if (scores.TryGetValue("田中", out int score))
{
Console.WriteLine(score);
}

3-6. Removeで要素を削除する

Dictionaryから要素を削除するには、Removeを使います。

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

scores.Add("田中", 80);
scores.Add("佐藤", 95);
scores.Add("鈴木", 70);

scores.Remove("佐藤");

これで、キーが"佐藤"の要素が削除されます。

削除後に確認すると、"佐藤"は存在しません。

C#
Console.WriteLine(scores.ContainsKey("佐藤")); // False

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

C#
bool removed = scores.Remove("山田");

Console.WriteLine(removed); // False

削除できたかどうかを判定したい場合は、戻り値を使いましょう。

3-7. ClearでDictionaryを空にする

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

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

scores.Add("田中", 80);
scores.Add("佐藤", 95);
scores.Add("鈴木", 70);

scores.Clear();

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

Clearを実行すると、Dictionaryの中身がすべて削除されます。

一部の要素だけ削除したい場合はRemove、すべて削除したい場合はClearを使います。

4. C# Dictionaryを使った実践的なサンプルコード

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

ユーザーIDからユーザー名を取得する例です。

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

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

int userId = 2;

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

実行結果は次のようになります。

ユーザー名: 佐藤

ユーザーIDのように、一意な番号からデータを取得したい場合、Dictionaryは非常に便利です。

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

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

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

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

string productCode = "B002";

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

実行結果は次のようになります。

価格: 2500円

商品コード、社員番号、会員IDなど、コードやIDをキーにして値を管理したい場面でDictionaryはよく使われます。

4-3. 文字列の出現回数をカウントする例

Dictionaryは、出現回数のカウントにもよく使われます。

次の例では、文字列の配列に含まれる単語の出現回数を数えます。

C#
string[] words = { "apple", "banana", "apple", "orange", "banana", "apple" };

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

foreach (string word in words)
{
if (counts.ContainsKey(word))
{
counts[word]++;
}
else
{
counts[word] = 1;
}
}

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

実行結果の例です。

apple: 3
banana: 2
orange: 1

このように、キーを単語、値を出現回数として管理できます。

TryGetValueを使うと、次のようにも書けます。

C#
foreach (string word in words)
{
if (counts.TryGetValue(word, out int count))
{
counts[word] = count + 1;
}
else
{
counts[word] = 1;
}
}

集計処理ではDictionaryが非常によく使われます。

4-4. foreachでDictionaryの中身をループ処理する

Dictionaryの中身を順番に処理するには、foreachを使います。

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

scores.Add("田中", 80);
scores.Add("佐藤", 95);
scores.Add("鈴木", 70);

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

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

田中: 80
佐藤: 95
鈴木: 70

型を明示して書くこともできます。

C#
foreach (KeyValuePair<string, int> item in scores)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}

ただし、通常はvarを使うことが多いです。

4-5. KeysとValuesを使ってキー・値だけを取り出す

Dictionaryでは、キーだけ、または値だけを取り出すこともできます。

キーだけを処理する場合はKeysを使います。

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

scores.Add("田中", 80);
scores.Add("佐藤", 95);
scores.Add("鈴木", 70);

foreach (string name in scores.Keys)
{
Console.WriteLine(name);
}

値だけを処理する場合はValuesを使います。

C#
foreach (int score in scores.Values)
{
Console.WriteLine(score);
}

キーと値の両方が必要な場合は、通常のforeachKeyValuePairを処理します。

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

5. C# Dictionaryで初心者がつまずきやすいポイント

5-1. 存在しないキーを取得すると例外が発生する

Dictionaryで初心者がよくつまずくのが、存在しないキーをインデクサで取得してしまうケースです。

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

scores.Add("田中", 80);

Console.WriteLine(scores["山田"]); // 例外が発生する

このコードでは、"山田"というキーが存在しないため例外が発生します。

安全に取得するには、TryGetValueを使いましょう。

C#
if (scores.TryGetValue("山田", out int score))
{
Console.WriteLine(score);
}
else
{
Console.WriteLine("キーが存在しません");
}

または、事前にContainsKeyで確認します。

C#
if (scores.ContainsKey("山田"))
{
Console.WriteLine(scores["山田"]);
}

ただし、値の取得まで行うならTryGetValueの方がおすすめです。

5-2. Addで同じキーを追加すると例外が発生する

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

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

scores.Add("田中", 80);
scores.Add("田中", 90); // 例外が発生する

既存の値を更新したい場合は、インデクサを使います。

C#
scores["田中"] = 90;

新規追加か更新かわからない場合も、インデクサを使うと便利です。

C#
scores["佐藤"] = 95; // なければ追加、あれば更新

ただし、「同じキーを誤って追加していないかチェックしたい」という場合は、あえてAddを使うこともあります。

5-3. キーは重複できない

Dictionaryでは、キーは重複できません。

次のように、同じキーに複数の値を別々に登録することはできません。

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

scores.Add("田中", 80);
scores.Add("田中", 90); // 不可

同じキーで複数の値を持ちたい場合は、値の型をListにする方法があります。

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

scores["田中"] = new List<int> { 80, 90, 85 };

この場合、"田中"というキーに対して、複数の点数をリストとして持てます。

C#
foreach (int score in scores["田中"])
{
Console.WriteLine(score);
}

キーは重複できないが、値の中に複数データを持たせることは可能です。

5-4. Dictionaryの順序は基本的に保証に依存しない方がよい

Dictionaryは、キーと値の対応を管理するためのコレクションです。
そのため、順番に意味を持たせる用途には向いていません。

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

scores.Add("田中", 80);
scores.Add("佐藤", 95);
scores.Add("鈴木", 70);

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

実行環境によっては追加した順番で出力されるように見えることもありますが、順序に依存したコードは避けた方が安全です。

順番を明確に扱いたい場合は、Listを使ったり、LINQで並び替えたりします。

C#
foreach (var item in scores.OrderBy(x => x.Key))
{
Console.WriteLine($"{item.Key}: {item.Value}");
}

また、キーの順序で管理したい場合は、SortedDictionarySortedListを検討することもあります。

5-5. nullをキーや値に使うときの注意点

C#のDictionaryでは、キーにnullを使うことはできません。

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

scores.Add(null, 100); // 例外が発生する

キーは値を探すための重要な情報なので、nullを指定しないようにしましょう。

一方、値にはnullを入れられる場合があります。
例えば、値の型がstringの場合です。

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

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

ただし、nullable reference typesを有効にしている場合は、stringstring?の違いに注意が必要です。

C#
Dictionary<int, string> names = new Dictionary<int, string>();
Dictionary<int, string?> nullableNames = new Dictionary<int, string?>();

nullを扱う可能性があるなら、TryGetValueと組み合わせて丁寧に判定しましょう。

5-6. 大文字・小文字を区別しないキー比較の方法

通常、Dictionary<string, TValue>では、大文字と小文字は区別されます。

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

scores["apple"] = 1;

Console.WriteLine(scores.ContainsKey("Apple")); // False

"apple""Apple"を同じキーとして扱いたい場合は、StringComparer.OrdinalIgnoreCaseを指定します。

C#
Dictionary<string, int> scores = new Dictionary<string, int>(
StringComparer.OrdinalIgnoreCase
);

scores["apple"] = 1;

Console.WriteLine(scores.ContainsKey("Apple")); // True
Console.WriteLine(scores["APPLE"]); // 1

英字のキーを扱う場合、大文字・小文字を区別するかどうかは重要です。

例えば、設定名、商品コード、ユーザー入力の文字列などでは、必要に応じてStringComparer.OrdinalIgnoreCaseを使いましょう。

6. JavaのHashMapとC#のDictionaryの違い

6-1. JavaではHashMap、C#ではDictionaryを使う

Javaでは、キーと値のペアを管理する代表的なクラスとしてHashMapを使います。

Java
HashMap<String, Integer> scores = new HashMap<>();
scores.put("Tanaka", 80);

C#では、同じような用途にDictionary<TKey, TValue>を使います。

C#
Dictionary<string, int> scores = new Dictionary<string, int>();
scores.Add("Tanaka", 80);

Java経験者がC#でHashMap相当の処理を書きたい場合は、まずDictionaryを使うと考えれば問題ありません。

6-2. JavaのputとC#のAdd・インデクサの違い

JavaのHashMapでは、要素の追加や更新にputを使います。

Java
map.put("apple", 100);
map.put("apple", 200); // 更新される

同じキーに対してputすると、値が上書きされます。

C#のDictionaryでは、Addとインデクサで挙動が異なります。

C#
map.Add("apple", 100);
map.Add("apple", 200); // 例外が発生する

Addは、同じキーが存在すると例外になります。

Javaのputに近い感覚で使いたい場合は、C#ではインデクサを使います。

C#
map["apple"] = 100;
map["apple"] = 200; // 更新される

Javaのputに近いのは、C#のmap[key] = valueです。

6-3. JavaのgetとC#のインデクサ・TryGetValueの違い

JavaのHashMapでは、値を取得するときにgetを使います。

Java
Integer score = map.get("Tanaka");

C#では、インデクサを使って取得します。

C#
int score = map["Tanaka"];

ただし、C#のインデクサは、キーが存在しない場合に例外が発生します。

そのため、安全に取得したい場合はTryGetValueを使います。

C#
if (map.TryGetValue("Tanaka", out int score))
{
Console.WriteLine(score);
}
else
{
Console.WriteLine("キーが存在しません");
}

Javaのgetに慣れている人は、C#ではTryGetValueの使い方を覚えておくと安全です。

6-4. 存在しないキーを取得したときの挙動の違い

JavaのHashMapで存在しないキーをgetすると、通常はnullが返ります。

Java
String name = map.get("unknown"); // null

一方、C#のDictionaryで存在しないキーをインデクサで取得すると例外が発生します。

C#
string name = map["unknown"]; // 例外が発生する

この違いは非常に重要です。

C#で存在しない可能性のあるキーを扱う場合は、次のように書きます。

C#
if (map.TryGetValue("unknown", out string? name))
{
Console.WriteLine(name);
}
else
{
Console.WriteLine("見つかりません");
}

Java経験者がC#に移るときは、「C#のDictionaryは存在しないキーをインデクサで読むと例外」と覚えておきましょう。

6-5. nullの扱いの違い

JavaのHashMapでは、nullキーやnull値を扱えます。

Java
HashMap<String, String> map = new HashMap<>();

map.put(null, "value");
map.put("key", null);

一方、C#のDictionaryでは、キーにnullは使えません。

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

map.Add(null, "value"); // 例外が発生する

値については、参照型やnullableな型であればnullを入れられます。

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

map.Add("key", null);

JavaのHashMapとC#のDictionaryでは、特にnullキーの扱いが異なります。

6-6. Java経験者がC#のDictionaryで注意すべきポイント

Java経験者がC#のDictionaryを使うときは、次の点に注意しましょう。

Java HashMapC# Dictionary
HashMap<K, V>Dictionary<TKey, TValue>
put(key, value)Add(key, value)またはdict[key] = value
get(key)dict[key]またはTryGetValue
存在しないキーはnullになりやすいインデクサでは例外
nullキーを使えるnullキーは使えない
同じキーのputは上書きAddは例外、インデクサは上書き

特に重要なのは、C#ではTryGetValueを積極的に使うことです。

C#
if (dict.TryGetValue(key, out var value))
{
// 見つかった場合の処理
}
else
{
// 見つからなかった場合の処理
}

JavaのHashMapと同じ感覚でC#のDictionaryを使うと、存在しないキーや重複キーで例外が発生することがあります。

7. DictionaryとHashtableの違い

7-1. Hashtableとは何か

C#にはHashtableというコレクションもあります。

C#
using System.Collections;

Hashtable table = new Hashtable();

table.Add("apple", 100);
table.Add("banana", 200);

Hashtableもキーと値のペアを管理するコレクションです。
名前だけを見ると、JavaのHashMapに近いもののように感じるかもしれません。

しかし、現在のC#では、多くの場合HashtableではなくDictionary<TKey, TValue>を使います。

7-2. Dictionaryはジェネリックで型安全に扱える

Hashtableは、キーや値をobjectとして扱います。

C#
Hashtable table = new Hashtable();

table.Add("apple", 100);

int price = (int)table["apple"];

値を取り出すときにキャストが必要です。
また、異なる型の値も入れられてしまいます。

C#
table.Add("banana", "二百円");
table.Add(123, true);

これに対して、Dictionary<TKey, TValue>は型を指定できます。

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

prices.Add("apple", 100);
prices.Add("banana", 200);

この場合、キーはstring、値はintに限定されます。

C#
prices.Add("orange", 300);
// prices.Add("grape", "三百円"); // コンパイルエラー

間違った型を入れようとするとコンパイル時に検出できるため、安全です。

7-3. HashtableよりDictionaryが推奨される理由

現在のC#では、通常HashtableよりDictionary<TKey, TValue>を使うことが推奨されます。

主な理由は次のとおりです。

比較項目HashtableDictionary
型安全性低い高い
キャスト必要になりやすい不要
ジェネリック非対応対応
読みやすさやや低い高い
現在の利用頻度既存コードで見かける新規コードでよく使う

Dictionaryは型を明確に指定できるため、コードのミスを減らせます。

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

この時点で、「キーは文字列」「値は整数」とはっきりわかります。

そのため、新しくコードを書く場合は、基本的にDictionary<TKey, TValue>を選びましょう。

7-4. 既存コードでHashtableを見かけたときの考え方

古いC#のコードや、古いライブラリではHashtableを見かけることがあります。

C#
Hashtable settings = new Hashtable();
settings["Theme"] = "Dark";
settings["FontSize"] = 14;

既存コードでHashtableを見かけた場合、すぐに悪いコードだと判断する必要はありません。
古い時代の書き方として使われている可能性があります。

ただし、新しく実装する場合やリファクタリングできる場合は、Dictionaryに置き換えられるか検討するとよいでしょう。

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

settings["Theme"] = "Dark";
settings["FontSize"] = 14;

値の型が統一できるなら、さらに型安全にできます。

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

settings["Theme"] = "Dark";
settings["Language"] = "ja";

Hashtableを見かけたら、「古いキー・値コレクションで、現在ならDictionaryを使うことが多い」と理解しておけばよいでしょう。

8. C# Dictionaryをより便利に使う応用テクニック

8-1. 初期化子を使ってDictionaryを作成する

Dictionaryは、初期化子を使って作成と同時に値を入れることができます。

C#
Dictionary<string, int> scores = new Dictionary<string, int>
{
{ "田中", 80 },
{ "佐藤", 95 },
{ "鈴木", 70 }
};

また、インデクサ形式の初期化もできます。

C#
Dictionary<string, int> scores = new Dictionary<string, int>
{
["田中"] = 80,
["佐藤"] = 95,
["鈴木"] = 70
};

この書き方は、設定値や固定データをあらかじめ用意したい場合に便利です。

C#
var statusMessages = new Dictionary<int, string>
{
[200] = "OK",
[404] = "Not Found",
[500] = "Internal Server Error"
};

短いDictionaryを作る場合は、初期化子を使うとコードが読みやすくなります。

8-2. Countで要素数を取得する

Dictionaryの要素数を取得するには、Countプロパティを使います。

C#
Dictionary<string, int> scores = new Dictionary<string, int>
{
["田中"] = 80,
["佐藤"] = 95,
["鈴木"] = 70
};

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

Countは、Dictionaryに登録されているキーと値のペアの数を返します。

要素があるかどうかを確認する場合にも使えます。

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

すべて削除すると、Count0になります。

C#
scores.Clear();

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

8-3. LINQと組み合わせて検索・並び替えする

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

例えば、点数が80点以上のデータだけを取り出す場合です。

C#
Dictionary<string, int> scores = new Dictionary<string, int>
{
["田中"] = 80,
["佐藤"] = 95,
["鈴木"] = 70,
["山田"] = 88
};

var highScores = scores.Where(x => x.Value >= 80);

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

キーで並び替える場合です。

C#
var orderedByName = scores.OrderBy(x => x.Key);

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

値で並び替える場合です。

C#
var orderedByScore = scores.OrderByDescending(x => x.Value);

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

Dictionary自体は順序に依存しない方がよいですが、表示や集計のタイミングでLINQを使って並び替えると便利です。

8-4. 値がListのDictionaryを作る

1つのキーに対して複数の値を持ちたい場合は、値の型をListにします。

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

groups["A班"] = new List<string> { "田中", "佐藤" };
groups["B班"] = new List<string> { "鈴木", "山田" };

値を追加する場合は、次のように書けます。

C#
groups["A班"].Add("高橋");

ただし、キーが存在しない状態でAddしようとすると例外になります。

C#
groups["C班"].Add("伊藤"); // 例外が発生する

安全に追加するには、キーがなければListを作成します。

C#
string groupName = "C班";
string memberName = "伊藤";

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

groups[groupName].Add(memberName);

TryGetValueを使うと、次のようにも書けます。

C#
if (!groups.TryGetValue(groupName, out List<string>? members))
{
members = new List<string>();
groups[groupName] = members;
}

members.Add(memberName);

グループ分け、カテゴリ分け、1対多のデータ管理では、Dictionary<TKey, List<TValue>>がよく使われます。

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

Dictionaryのキーには、stringintだけでなく独自クラスも使えます。

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

ただし、独自クラスをキーにする場合は、同一判定に注意が必要です。

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

var user1 = new User { Id = 1, Name = "田中" };
var user2 = new User { Id = 1, Name = "田中" };

map[user1] = "管理者";

Console.WriteLine(map.ContainsKey(user2)); // Falseになる場合がある

user1user2は中身が同じでも、別のインスタンスとして扱われます。

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

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

public override bool Equals(object? obj)
{
if (obj is not User other)
{
return false;
}

return Id == other.Id;
}

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

ただし、初心者のうちは、キーにはintstringなどのシンプルな型を使うのがおすすめです。

独自クラスをキーにする必要がある場合は、同一判定とハッシュコードの扱いを理解してから使いましょう。

8-6. ConcurrentDictionaryを使うべきケース

通常のDictionaryは、複数のスレッドから同時に書き込む用途には向いていません。

例えば、複数の処理が同時に同じDictionaryを更新するような場合、不具合が起きる可能性があります。

そのような場面では、ConcurrentDictionary<TKey, TValue>を検討します。

C#
using System.Collections.Concurrent;

ConcurrentDictionary<string, int> counts = new ConcurrentDictionary<string, int>();

counts.TryAdd("apple", 1);
counts.AddOrUpdate("apple", 1, (key, oldValue) => oldValue + 1);

Console.WriteLine(counts["apple"]);

ConcurrentDictionaryは、マルチスレッド環境で安全に使いやすいDictionaryです。

ただし、通常のアプリケーションや単純な処理では、まずDictionaryで十分です。

状況使う候補
通常のキー・値管理Dictionary
複数スレッドから同時に更新するConcurrentDictionary
キー順に並べたいSortedDictionary
古いコードとの互換性Hashtable

必要になったときにConcurrentDictionaryを検討すれば問題ありません。

9. C#のHashMap・Dictionaryに関するよくある質問

9-1. C#にHashMapはありますか?

C#の標準的なコレクションには、JavaのようなHashMapという名前のクラスは基本的にありません。

C#でHashMap相当の処理をしたい場合は、Dictionary<TKey, TValue>を使います。

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

map["apple"] = 100;
map["banana"] = 200;

「C# HashMap」と検索している場合、ほとんどのケースで知りたい内容はDictionaryの使い方です。

9-2. DictionaryとMapは同じ意味ですか?

厳密には同じではありませんが、近い意味で使われることがあります。

Mapは、キーと値を対応付けるデータ構造の一般的な呼び方です。
JavaではMapインターフェースがあり、その実装としてHashMapがあります。

C#では、同じような用途にDictionary<TKey, TValue>があります。

つまり、初心者向けにざっくり言うと、次のように理解できます。

JavaのMap / HashMapに近いもの = C#のDictionary

ただし、C#ではクラス名としてMapではなくDictionaryを使うのが一般的です。

9-3. DictionaryとListはどちらを使うべきですか?

キーで値を取り出したいならDictionary、順番にデータを扱いたいならListを使います。

例えば、ユーザーIDから名前を取得したい場合はDictionaryが向いています。

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

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

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

一方、名前を順番に並べたいだけならListが向いています。

C#
List<string> names = new List<string>();

names.Add("田中");
names.Add("佐藤");
names.Add("鈴木");

foreach (string name in names)
{
Console.WriteLine(name);
}

判断基準はシンプルです。

条件選ぶもの
IDや名前などのキーで探したいDictionary
順番に並べて処理したいList
重複した値をそのまま持ちたいList
キーを重複させたくないDictionary

9-4. Dictionaryでキーが存在しない場合はどうすればよいですか?

キーが存在しない可能性がある場合は、TryGetValueを使います。

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

scores["田中"] = 80;

if (scores.TryGetValue("山田", out int score))
{
Console.WriteLine(score);
}
else
{
Console.WriteLine("キーが存在しません");
}

インデクサで存在しないキーを取得すると例外が発生します。

C#
Console.WriteLine(scores["山田"]); // 例外が発生する

そのため、外部入力や検索結果など、キーの存在が確実ではない場合はTryGetValueを使うのが安全です。

9-5. JavaのHashMapと同じようにC#で書くにはどうすればよいですか?

JavaのHashMapに近い書き方をC#でするなら、Dictionary<TKey, TValue>とインデクサを使います。

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

Java
HashMap<String, Integer> map = new HashMap<>();

map.put("apple", 100);
map.put("banana", 200);

Integer price = map.get("apple");

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

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

map["apple"] = 100;
map["banana"] = 200;

int price = map["apple"];

ただし、存在しないキーを取得する可能性がある場合は、C#ではTryGetValueを使いましょう。

C#
if (map.TryGetValue("apple", out int price))
{
Console.WriteLine(price);
}
else
{
Console.WriteLine("キーが存在しません");
}

Javaのputに近いのは、C#の次の書き方です。

C#
map[key] = value;

Javaのgetに近い取得処理は、単純には次の書き方です。

C#
var value = map[key];

ただし、安全性を考えるなら、C#ではTryGetValueを覚えておくとよいでしょう。

まとめ

C#でHashMapを使いたい場合、基本的にはDictionary<TKey, TValue>を使います。

JavaにはHashMapがありますが、C#の標準的な書き方ではDictionaryがその役割を担います。

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

map["apple"] = 100;
map["banana"] = 200;

Console.WriteLine(map["apple"]); // 100

Dictionaryは、キーと値のペアでデータを管理するコレクションです。
ユーザーIDと名前、商品コードと価格、単語と出現回数など、キーから値をすばやく取得したい場面で活躍します。

基本操作は次のとおりです。

操作書き方
追加dict.Add(key, value)
追加・更新dict[key] = value
取得dict[key]
安全に取得dict.TryGetValue(key, out value)
存在確認dict.ContainsKey(key)
削除dict.Remove(key)
全削除dict.Clear()
件数取得dict.Count

初心者が特に注意すべき点は、存在しないキーをインデクサで取得すると例外が発生すること、Addで同じキーを追加すると例外が発生すること、キーは重複できないことです。

Java経験者は、次の違いを意識すると理解しやすくなります。

JavaのHashMap       → C#のDictionary
Javaのput → C#のAddまたはインデクサ
Javaのget → C#のインデクサまたはTryGetValue
存在しないキー → C#のインデクサでは例外

また、C#にはHashtableもありますが、現在の新規コードでは、型安全で使いやすいDictionary<TKey, TValue>を使うのが一般的です。

C#でHashMap相当の処理を書きたいときは、まずDictionaryを使う。
そして、安全に値を取得したいときはTryGetValueを使う。

この2つを押さえておけば、C#のキー・値管理はかなり扱いやすくなります。