C#の辞書(Dictionary)とは?宣言・追加・取得・ループ処理まで初心者向けに徹底解説
はじめに
C#で複数のデータを管理するとき、配列やListと並んでよく使われるのが辞書型のDictionaryです。
Dictionaryは、商品コードから商品名を取得する、ユーザーIDからユーザー情報を検索する、単語ごとの出現回数を数えるといった処理に適しています。番号ではなく、意味のある「キー」を使って値を管理できる点が大きな特徴です。
この記事では、C#の辞書であるDictionaryについて、宣言、初期化、追加、更新、取得、削除、ループ処理まで初心者向けに解説します。よくあるエラーや実践的なサンプルコードも紹介するので、基本から順番に確認していきましょう。
1. C#の辞書(Dictionary)とは?
1-1. Dictionaryはキーと値をセットで管理するコレクション
C#のDictionaryは、「キー」と「値」をセットで管理するコレクションです。
たとえば、商品名と価格を管理する場合は、次のような対応関係を作れます。
C#"りんご" → 150
"バナナ" → 120
"みかん" → 200
C#のコードでは、次のように記述します。
C#Dictionary<string, int> prices = new Dictionary<string, int>
{
{ "りんご", 150 },
{ "バナナ", 120 },
{ "みかん", 200 }
};
この例では、商品名がキー、価格が値です。
キーを指定すると、対応する値を取得できます。
C#Console.WriteLine(prices["りんご"]);
実行結果は次のとおりです。
150
このように、Dictionaryを使うと「りんごは何円か」という情報をキーから直接検索できます。
1-2. 配列・Listとの違い
配列やListは、基本的に0から始まるインデックスを使って要素を取得します。
C#List<string> fruits = new List<string>
{
"りんご",
"バナナ",
"みかん"
};
Console.WriteLine(fruits[0]);
一方、Dictionaryでは任意のキーを使って値を取得します。
C#Dictionary<string, int> prices = new Dictionary<string, int>
{
{ "りんご", 150 },
{ "バナナ", 120 }
};
Console.WriteLine(prices["りんご"]);
主な違いは次のとおりです。
| コレクション | 要素の取得方法 | 向いている用途 |
|---|---|---|
| 配列 | 数値インデックス | 要素数が固定されたデータ |
| List | 数値インデックス | 要素を追加・削除する一覧データ |
| Dictionary | 任意のキー | キーと値の対応関係を管理するデータ |
単純に順番どおりの一覧を扱いたい場合はList、特定の名前やIDから値を検索したい場合はDictionaryが適しています。
1-3. Dictionaryを使うメリット
Dictionaryを使う主なメリットは、次の3点です。
1つ目は、意味のあるキーで値を管理できることです。
C#userNames[1001]
prices["りんご"]
settings["Theme"]
数値インデックスだけでなく、文字列やIDなどを使って値を取得できます。
2つ目は、キーによる検索が高速であることです。多くの場合、先頭から順番に探す必要がないため、大量のデータから目的の値を効率的に取得できます。
3つ目は、キーの重複を防げることです。同じキーは複数登録できないため、ユーザーIDや商品コードなど、一意であるべき情報の管理に向いています。
1-4. Dictionaryが向いている場面
Dictionaryは、次のような場面に向いています。
商品コードと商品情報を対応させる
ユーザーIDとユーザー名を対応させる
都道府県コードと都道府県名を対応させる
単語ごとの出現回数を記録する
設定名と設定値を管理する
ファイル名とファイル情報を対応させる
たとえば、ユーザーIDから名前を取得する処理は次のように書けます。
C#Dictionary<int, string> users = new Dictionary<int, string>
{
{ 1001, "田中" },
{ 1002, "佐藤" },
{ 1003, "鈴木" }
};
Console.WriteLine(users[1002]);
実行結果は次のとおりです。
佐藤
「特定のキーから対応する値を取得したい」という要件がある場合は、Dictionaryの利用を検討しましょう。
2. Dictionaryの基本構文と宣言方法
2-1. Dictionary<TKey, TValue>の意味
Dictionaryの基本構文は次のとおりです。
C#Dictionary<TKey, TValue>
TKeyにはキーの型、TValueには値の型を指定します。
たとえば、キーが文字列、値が整数の場合は次のように宣言します。
C#Dictionary<string, int> prices;
キーが整数、値が文字列の場合は次のようになります。
C#Dictionary<int, string> users;
TKeyとTValueには、具体的な型を指定します。
C#Dictionary<string, string>
Dictionary<string, int>
Dictionary<int, string>
Dictionary<int, bool>
C#のジェネリック機能によって型が決まるため、異なる型の値を誤って登録しようとするとコンパイルエラーになります。
C#Dictionary<string, int> prices = new Dictionary<string, int>();
prices.Add("りんご", 150);
// prices.Add("バナナ", "120"); // コンパイルエラー
2-2. Dictionaryを使うために必要なusing
DictionaryはSystem.Collections.Generic名前空間に定義されています。
そのため、通常はソースコードの先頭に次のusingを記述します。
C#using System.Collections.Generic;
コンソール出力も行う場合は、次のように記述します。
C#using System;
using System.Collections.Generic;
最近の.NETプロジェクトでは、暗黙的なusingによって記述しなくても使用できる場合があります。ただし、Dictionaryが見つからないというエラーが発生した場合は、using System.Collections.Generic;が記述されているか確認しましょう。
完全修飾名を使う場合は、usingを書かずに次のように宣言することもできます。
C#System.Collections.Generic.Dictionary<string, int> prices =
new System.Collections.Generic.Dictionary<string, int>();
一般的には、usingを記述したほうがコードを読みやすくできます。
2-3. 空のDictionaryを宣言する方法
空のDictionaryは次のように宣言します。
C#Dictionary<string, int> prices = new Dictionary<string, int>();
この時点では要素が1件も登録されていません。
C#Console.WriteLine(prices.Count);
実行結果は次のとおりです。
0
型を右辺から推論できる場合は、varも使用できます。
C#var prices = new Dictionary<string, int>();
また、比較的新しいC#では、ターゲット型が明確な場合に次のような省略記法も使用できます。
C#Dictionary<string, int> prices = new();
いずれの書き方でも、作成されるDictionaryの内容は同じです。
2-4. 初期値を入れてDictionaryを作成する方法
Dictionaryの作成と同時に初期値を登録するには、コレクション初期化子を使用します。
C#Dictionary<string, int> prices = new Dictionary<string, int>
{
{ "りんご", 150 },
{ "バナナ", 120 },
{ "みかん", 200 }
};
インデクサー形式で初期化することもできます。
C#Dictionary<string, int> prices = new Dictionary<string, int>
{
["りんご"] = 150,
["バナナ"] = 120,
["みかん"] = 200
};
どちらも、キーと値をセットで登録する書き方です。
すでに存在するDictionaryの内容をコピーして、新しいDictionaryを作ることもできます。
C#Dictionary<string, int> original = new Dictionary<string, int>
{
{ "りんご", 150 },
{ "バナナ", 120 }
};
Dictionary<string, int> copied =
new Dictionary<string, int>(original);
この場合、copiedにはoriginalと同じキーと値が登録されます。
2-5. キーと値に使える型の例
Dictionaryのキーと値には、さまざまな型を指定できます。
文字列をキーにする例は次のとおりです。
C#Dictionary<string, int> prices = new();
整数をキーにする例は次のとおりです。
C#Dictionary<int, string> users = new();
列挙型をキーにすることもできます。
C#enum Status
{
Pending,
Completed,
Canceled
}
Dictionary<Status, string> messages = new()
{
{ Status.Pending, "処理待ち" },
{ Status.Completed, "完了" },
{ Status.Canceled, "キャンセル" }
};
値には、クラスやListなども指定できます。
C#Dictionary<int, List<string>> userSkills = new();
ただし、キーには「同じキーかどうかを正しく判定できる型」を使う必要があります。文字列、数値、列挙型などは比較的扱いやすいキーです。
3. Dictionaryに要素を追加・更新する方法
3-1. Addメソッドで要素を追加する
Dictionaryに要素を追加する基本的な方法は、Addメソッドです。
C#Dictionary<string, int> prices = new Dictionary<string, int>();
prices.Add("りんご", 150);
prices.Add("バナナ", 120);
Addメソッドの第1引数にキー、第2引数に値を指定します。
C#prices.Add("みかん", 200);
追加した値は、キーを使って取得できます。
C#Console.WriteLine(prices["みかん"]);
実行結果は次のとおりです。
200
3-2. インデクサーで要素を追加する
角括弧を使ったインデクサーでも、要素を追加できます。
C#Dictionary<string, int> prices = new Dictionary<string, int>();
prices["りんご"] = 150;
prices["バナナ"] = 120;
指定したキーが存在しない場合は、新しい要素として追加されます。
C#prices["みかん"] = 200;
Addとインデクサーには、既存キーを指定したときの動作に違いがあります。
Addは既存キーを指定すると例外が発生するインデクサーは既存キーを指定すると値を上書きする
追加のみを許可したい場合はAdd、追加と更新を同じ書き方で行いたい場合はインデクサーが便利です。
3-3. 既存キーの値を更新する
登録済みの値を更新するには、インデクサーを使用します。
C#Dictionary<string, int> prices = new Dictionary<string, int>
{
{ "りんご", 150 }
};
prices["りんご"] = 180;
Console.WriteLine(prices["りんご"]);
実行結果は次のとおりです。
180
更新前にキーが存在するか確認したい場合は、ContainsKeyを使います。
C#if (prices.ContainsKey("りんご"))
{
prices["りんご"] = 180;
}
指定したキーがなければ追加し、あれば更新する処理は、インデクサーだけで実現できます。
C#prices["りんご"] = 180;
キーが登録済みかどうかに関係なく代入できるため、設定値の登録や更新などでよく使われます。
3-4. 同じキーを追加したときのエラー
Addメソッドで同じキーを2回追加すると、ArgumentExceptionが発生します。
C#Dictionary<string, int> prices = new Dictionary<string, int>();
prices.Add("りんご", 150);
prices.Add("りんご", 180);
Dictionaryでは、1つのキーにつき1つの値しか登録できません。
同じキーの値を変更したい場合は、Addではなくインデクサーを使用します。
C#prices["りんご"] = 180;
また、例外を発生させずに追加を試みたい場合は、TryAddも利用できます。
C#bool added = prices.TryAdd("りんご", 200);
if (!added)
{
Console.WriteLine("同じキーがすでに登録されています。");
}
TryAddは追加に成功するとtrue、同じキーが存在して追加できない場合はfalseを返します。
3-5. ContainsKeyで重複追加を防ぐ
キーが登録済みかどうかは、ContainsKeyメソッドで確認できます。
C#Dictionary<string, int> prices = new Dictionary<string, int>();
if (!prices.ContainsKey("りんご"))
{
prices.Add("りんご", 150);
}
ContainsKeyは、キーが存在する場合にtrue、存在しない場合にfalseを返します。
C#bool exists = prices.ContainsKey("りんご");
Console.WriteLine(exists);
ただし、単純に重複を避けながら追加したいだけであれば、TryAddを使うと処理を簡潔にできます。
C#if (prices.TryAdd("りんご", 150))
{
Console.WriteLine("追加しました。");
}
else
{
Console.WriteLine("すでに登録されています。");
}
4. Dictionaryから値を取得する方法
4-1. キーを指定して値を取得する
Dictionaryから値を取得する最も基本的な方法は、インデクサーを使う方法です。
C#Dictionary<string, int> prices = new Dictionary<string, int>
{
{ "りんご", 150 },
{ "バナナ", 120 }
};
int applePrice = prices["りんご"];
Console.WriteLine(applePrice);
実行結果は次のとおりです。
150
キーが確実に存在すると分かっている場合は、シンプルなインデクサーが便利です。
4-2. 存在しないキーを指定したときのエラー
インデクサーで存在しないキーを指定すると、KeyNotFoundExceptionが発生します。
C#Dictionary<string, int> prices = new Dictionary<string, int>
{
{ "りんご", 150 }
};
Console.WriteLine(prices["メロン"]);
メロンというキーが登録されていないため、プログラムは正常に値を取得できません。
ユーザー入力や外部データをキーに使う場合は、目的のキーが存在するとは限りません。そのような場面では、インデクサーで直接取得するのではなく、TryGetValueを使うのが安全です。
4-3. TryGetValueで安全に値を取得する
TryGetValueを使うと、例外を発生させずに値を取得できます。
C#Dictionary<string, int> prices = new Dictionary<string, int>
{
{ "りんご", 150 },
{ "バナナ", 120 }
};
if (prices.TryGetValue("りんご", out int price))
{
Console.WriteLine($"価格は{price}円です。");
}
else
{
Console.WriteLine("商品が見つかりません。");
}
TryGetValueは、指定したキーが存在するとtrueを返し、対応する値をout引数へ格納します。
キーが存在しない場合はfalseを返します。
C#if (prices.TryGetValue("メロン", out int price))
{
Console.WriteLine(price);
}
else
{
Console.WriteLine("メロンは登録されていません。");
}
ユーザーが入力したキーを検索するときは、TryGetValueを優先すると安全なコードになります。
4-4. ContainsKeyとTryGetValueの使い分け
ContainsKeyとTryGetValueは、どちらもキーの存在を確認できます。
ContainsKeyを使った取得処理は次のとおりです。
C#if (prices.ContainsKey("りんご"))
{
int price = prices["りんご"];
Console.WriteLine(price);
}
このコードでは、キーの確認と値の取得を別々に行っています。
一方、TryGetValueなら1回の処理で存在確認と値の取得ができます。
C#if (prices.TryGetValue("りんご", out int price))
{
Console.WriteLine(price);
}
値も必要な場合は、基本的にTryGetValueが適しています。
ContainsKeyは、キーの存在だけを調べたい場合や、存在の有無によって追加・更新の処理を分けたい場合に便利です。
C#if (prices.ContainsKey("りんご"))
{
Console.WriteLine("登録済みです。");
}
4-5. 値だけ・キーだけを取得する方法
Dictionaryのキーだけを取得するには、Keysプロパティを使用します。
C#foreach (string key in prices.Keys)
{
Console.WriteLine(key);
}
値だけを取得するには、Valuesプロパティを使用します。
C#foreach (int value in prices.Values)
{
Console.WriteLine(value);
}
キーや値をListに変換することもできます。LINQの拡張メソッドを使用する場合は、System.Linqを読み込みます。
C#using System.Linq;
List<string> keys = prices.Keys.ToList();
List<int> values = prices.Values.ToList();
すべてのキーまたは値をまとめて処理したい場合に便利です。
5. Dictionaryの要素を削除・確認する方法
5-1. Removeで指定したキーの要素を削除する
特定の要素を削除するには、Removeメソッドを使用します。
C#Dictionary<string, int> prices = new Dictionary<string, int>
{
{ "りんご", 150 },
{ "バナナ", 120 }
};
bool removed = prices.Remove("りんご");
削除に成功するとtrue、指定したキーが存在しない場合はfalseを返します。
C#if (prices.Remove("りんご"))
{
Console.WriteLine("削除しました。");
}
else
{
Console.WriteLine("指定したキーは存在しません。");
}
削除した値も必要な場合は、対応するオーバーロードを利用できます。
C#if (prices.Remove("りんご", out int removedPrice))
{
Console.WriteLine($"{removedPrice}円の商品を削除しました。");
}
5-2. Clearですべての要素を削除する
Dictionaryの全要素を削除するには、Clearメソッドを使用します。
C#prices.Clear();
削除後の要素数は0になります。
C#Console.WriteLine(prices.Count);
実行結果は次のとおりです。
0
Clearを実行しても、変数そのものがなくなるわけではありません。空になったDictionaryへ、再び要素を追加できます。
C#prices.Clear();
prices.Add("メロン", 500);
5-3. Countで要素数を確認する
Dictionaryに登録されている要素数は、Countプロパティで確認できます。
C#Dictionary<string, int> prices = new Dictionary<string, int>
{
{ "りんご", 150 },
{ "バナナ", 120 },
{ "みかん", 200 }
};
Console.WriteLine(prices.Count);
実行結果は次のとおりです。
3
空かどうかを確認する場合は、Countが0かどうかを判定します。
C#if (prices.Count == 0)
{
Console.WriteLine("商品が登録されていません。");
}
1件以上あるか確認する場合は、次のように書けます。
C#if (prices.Count > 0)
{
Console.WriteLine("商品が登録されています。");
}
5-4. ContainsValueで値の存在を確認する
特定の値が登録されているか確認するには、ContainsValueメソッドを使用します。
C#Dictionary<string, int> prices = new Dictionary<string, int>
{
{ "りんご", 150 },
{ "バナナ", 120 }
};
bool exists = prices.ContainsValue(150);
Console.WriteLine(exists);
実行結果は次のとおりです。
True
ただし、Dictionaryはキーによる検索を得意とするコレクションです。値の検索では、複数の要素を順番に調べる必要があります。
値から頻繁にキーを検索する設計になっている場合は、キーと値の関係を見直すか、逆引き用のDictionaryを別に用意することも検討しましょう。
6. Dictionaryをループ処理する方法
6-1. foreachでキーと値を取り出す
Dictionaryの全要素を処理するには、foreachを使用します。
C#Dictionary<string, int> prices = new Dictionary<string, int>
{
{ "りんご", 150 },
{ "バナナ", 120 },
{ "みかん", 200 }
};
foreach (KeyValuePair<string, int> item in prices)
{
Console.WriteLine($"{item.Key}: {item.Value}円");
}
item.Keyでキー、item.Valueで値を取得できます。
varを使うと型名を省略できます。
C#foreach (var item in prices)
{
Console.WriteLine($"{item.Key}: {item.Value}円");
}
分解構文を使って、キーと値を別々の変数として受け取ることもできます。
C#foreach (var (name, price) in prices)
{
Console.WriteLine($"{name}: {price}円");
}
6-2. Keysを使ってキーだけをループする
キーだけを処理したい場合は、Keysを使用します。
C#foreach (string name in prices.Keys)
{
Console.WriteLine(name);
}
キーから値も取得できます。
C#foreach (string name in prices.Keys)
{
Console.WriteLine($"{name}: {prices[name]}円");
}
ただし、キーと値の両方が必要な場合は、Dictionary自体をforeachで処理したほうが分かりやすく効率的です。
C#foreach (var item in prices)
{
Console.WriteLine($"{item.Key}: {item.Value}円");
}
6-3. Valuesを使って値だけをループする
値だけを処理したい場合は、Valuesを使用します。
C#foreach (int price in prices.Values)
{
Console.WriteLine(price);
}
すべての価格を合計する例は次のとおりです。
C#int total = 0;
foreach (int price in prices.Values)
{
total += price;
}
Console.WriteLine($"合計: {total}円");
LINQを使用すれば、より簡潔に合計できます。
C#using System.Linq;
int total = prices.Values.Sum();
6-4. KeyValuePairの意味
Dictionaryをforeachでループすると、各要素はKeyValuePair<TKey, TValue>として取り出されます。
C#KeyValuePair<string, int>
これは、1組のキーと値を表す型です。
C#foreach (KeyValuePair<string, int> item in prices)
{
string key = item.Key;
int value = item.Value;
Console.WriteLine($"{key}: {value}");
}
KeyValuePairのKeyとValueは読み取り専用です。ループ中に次のような直接代入はできません。
C#foreach (var item in prices)
{
// item.Value = 200; // 代入できない
}
値を更新する場合は、キーを使ってDictionaryのインデクサーへ代入します。ただし、ループ中の変更には注意が必要です。
6-5. ループ中に要素を削除するときの注意点
foreachでDictionaryを処理している最中に要素を追加または削除すると、例外が発生する可能性があります。
次のようなコードは避けましょう。
C#foreach (var item in prices)
{
if (item.Value < 150)
{
prices.Remove(item.Key);
}
}
安全に削除するには、削除するキーを先に別のリストへ保存します。
C#List<string> keysToRemove = new List<string>();
foreach (var item in prices)
{
if (item.Value < 150)
{
keysToRemove.Add(item.Key);
}
}
foreach (string key in keysToRemove)
{
prices.Remove(key);
}
LINQを使ってキーのコピーを作る方法もあります。
C#using System.Linq;
foreach (string key in prices
.Where(item => item.Value < 150)
.Select(item => item.Key)
.ToList())
{
prices.Remove(key);
}
ToListによって対象キーをコピーしてから削除するため、元のDictionaryを列挙しながら直接変更することを避けられます。
7. Dictionaryのよく使う実践例
7-1. 商品名と価格を管理する
商品名と価格の対応関係は、Dictionary<string, int>で管理できます。
C#Dictionary<string, int> prices = new Dictionary<string, int>
{
{ "ノート", 200 },
{ "ペン", 120 },
{ "消しゴム", 100 }
};
string productName = "ペン";
if (prices.TryGetValue(productName, out int price))
{
Console.WriteLine($"{productName}の価格は{price}円です。");
}
else
{
Console.WriteLine("商品が見つかりません。");
}
価格だけでなく、在庫数や説明も管理したい場合は、値にクラスを指定します。
C#class Product
{
public string Name { get; set; } = "";
public int Price { get; set; }
public int Stock { get; set; }
}
C#Dictionary<string, Product> products = new Dictionary<string, Product>
{
{
"P001",
new Product
{
Name = "ノート",
Price = 200,
Stock = 15
}
},
{
"P002",
new Product
{
Name = "ペン",
Price = 120,
Stock = 30
}
}
};
if (products.TryGetValue("P001", out Product? product))
{
Console.WriteLine($"{product.Name}: {product.Price}円");
}
商品コードをキーにすると、コードから商品情報を効率的に取得できます。
7-2. ユーザーIDと名前を管理する
ユーザーIDは重複しないことが多いため、Dictionaryのキーに適しています。
C#Dictionary<int, string> users = new Dictionary<int, string>
{
{ 1001, "田中太郎" },
{ 1002, "佐藤花子" },
{ 1003, "鈴木一郎" }
};
int userId = 1002;
if (users.TryGetValue(userId, out string? userName))
{
Console.WriteLine($"ユーザー名: {userName}");
}
else
{
Console.WriteLine("ユーザーが見つかりません。");
}
ユーザーの複数の情報を扱う場合は、値に独自クラスを指定します。
C#class User
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Email { get; set; } = "";
}
C#Dictionary<int, User> users = new Dictionary<int, User>
{
{
1001,
new User
{
Id = 1001,
Name = "田中太郎",
Email = "tanaka@example.com"
}
}
};
7-3. 文字列の出現回数をカウントする
Dictionaryは、文字や単語の出現回数を数える処理にも適しています。
C#string[] words =
{
"C#",
"Dictionary",
"C#",
"List",
"C#",
"Dictionary"
};
Dictionary<string, int> counts = new Dictionary<string, int>();
foreach (string word in words)
{
if (counts.ContainsKey(word))
{
counts[word]++;
}
else
{
counts.Add(word, 1);
}
}
foreach (var item in counts)
{
Console.WriteLine($"{item.Key}: {item.Value}回");
}
実行結果は次のようになります。
C#: 3回
Dictionary: 2回
List: 1回
TryGetValueを使うと、次のようにも書けます。
C#foreach (string word in words)
{
if (counts.TryGetValue(word, out int count))
{
counts[word] = count + 1;
}
else
{
counts[word] = 1;
}
}
7-4. 設定値をキーで管理する
アプリケーションの設定値を、設定名と値の組み合わせで管理できます。
C#Dictionary<string, string> settings =
new Dictionary<string, string>
{
{ "Theme", "Dark" },
{ "Language", "ja" },
{ "PageSize", "20" }
};
if (settings.TryGetValue("Theme", out string? theme))
{
Console.WriteLine($"テーマ: {theme}");
}
設定値がすべて文字列の場合、数値として使う際は変換が必要です。
C#if (settings.TryGetValue("PageSize", out string? pageSizeText)
&& int.TryParse(pageSizeText, out int pageSize))
{
Console.WriteLine($"表示件数: {pageSize}");
}
値の型が複数になる場合は、専用の設定クラスを作る方法も検討しましょう。
7-5. ListとDictionaryを組み合わせて使う
Dictionaryの値としてListを指定すると、1つのキーに複数の値を関連付けられます。
C#Dictionary<string, List<string>> categoryProducts =
new Dictionary<string, List<string>>
{
{
"文房具",
new List<string>
{
"ノート",
"ペン",
"消しゴム"
}
},
{
"食品",
new List<string>
{
"りんご",
"バナナ"
}
}
};
特定のカテゴリの商品を表示する例は次のとおりです。
C#if (categoryProducts.TryGetValue(
"文房具",
out List<string>? products))
{
foreach (string product in products)
{
Console.WriteLine(product);
}
}
既存カテゴリへ商品を追加する処理は、次のように書けます。
C#string category = "文房具";
string newProduct = "定規";
if (!categoryProducts.TryGetValue(
category,
out List<string>? products))
{
products = new List<string>();
categoryProducts[category] = products;
}
products.Add(newProduct);
このような構造は、カテゴリ別の商品一覧、部署別の社員一覧、ユーザー別の権限一覧などに利用できます。
8. Dictionaryでよくあるエラーと対処法
8-1. 同じキーを追加してしまう
Addで同じキーを追加すると、ArgumentExceptionが発生します。
C#prices.Add("りんご", 150);
prices.Add("りんご", 180);
追加前に確認する場合は、ContainsKeyを使用します。
C#if (!prices.ContainsKey("りんご"))
{
prices.Add("りんご", 150);
}
より簡潔に書くなら、TryAddを使用します。
C#if (!prices.TryAdd("りんご", 150))
{
Console.WriteLine("すでに登録されています。");
}
既存の値を更新したい場合は、インデクサーを使います。
C#prices["りんご"] = 180;
8-2. 存在しないキーを取得してしまう
存在しないキーをインデクサーで取得すると、KeyNotFoundExceptionが発生します。
C#int price = prices["メロン"];
外部入力など、キーの存在が保証されない場合はTryGetValueを使用します。
C#if (prices.TryGetValue("メロン", out int price))
{
Console.WriteLine(price);
}
else
{
Console.WriteLine("商品が見つかりません。");
}
初期値を返したい場合は、次のように書けます。
C#int price = prices.TryGetValue("メロン", out int value)
? value
: 0;
8-3. nullをキーに指定してしまう
Dictionaryのキーにnullを指定することはできません。
C#Dictionary<string, int> prices = new();
// prices.Add(null, 100);
参照型のキーへnullを渡すと、実行時にArgumentNullExceptionが発生します。
外部から受け取った文字列をキーとして使う場合は、事前に確認しましょう。
C#string? productName = GetProductName();
if (!string.IsNullOrEmpty(productName))
{
prices[productName] = 100;
}
空文字列はキーとして登録できますが、データの意味が分かりにくくなる可能性があります。通常は、空文字列も避けたほうが安全です。
8-4. キーの大文字・小文字違いで取得できない
文字列をキーにしたDictionaryでは、通常、大文字と小文字が区別されます。
C#Dictionary<string, string> settings =
new Dictionary<string, string>
{
{ "Theme", "Dark" }
};
Console.WriteLine(settings.ContainsKey("theme"));
実行結果はFalseです。
大文字と小文字を区別したくない場合は、作成時にStringComparerを指定します。
C#Dictionary<string, string> settings =
new Dictionary<string, string>(
StringComparer.OrdinalIgnoreCase)
{
{ "Theme", "Dark" }
};
Console.WriteLine(settings.ContainsKey("theme"));
この場合、実行結果はTrueになります。
一般的な設定名や識別子で大文字と小文字を無視したい場合は、StringComparer.OrdinalIgnoreCaseがよく使われます。
8-5. ループ中にDictionaryを変更してしまう
foreachで列挙中のDictionaryへ要素を追加または削除すると、処理が不安定になったり例外が発生したりする可能性があります。
C#foreach (var item in prices)
{
prices.Add("追加商品", 300);
}
変更が必要な場合は、追加または削除する内容を別のコレクションへ保存し、ループ終了後に反映します。
C#List<string> keysToRemove = new List<string>();
foreach (var item in prices)
{
if (item.Value <= 100)
{
keysToRemove.Add(item.Key);
}
}
foreach (string key in keysToRemove)
{
prices.Remove(key);
}
または、ToListで列挙対象のコピーを作ります。
C#using System.Linq;
foreach (var item in prices.ToList())
{
if (item.Value <= 100)
{
prices.Remove(item.Key);
}
}
9. Dictionaryを使うときの注意点
9-1. キーは重複できない
Dictionaryでは、同じキーを複数登録できません。
C#Dictionary<int, string> users = new();
users.Add(1001, "田中");
// users.Add(1001, "佐藤"); // 同じキーなのでエラー
一方、値は重複しても問題ありません。
C#users.Add(1002, "田中");
複数の値を1つのキーへ関連付けたい場合は、値にListなどを指定します。
C#Dictionary<string, List<string>> groups = new();
9-2. キーの検索は高速だが順序管理には向かない
Dictionaryは、キーから値を効率的に検索するためのコレクションです。
C#int price = prices["りんご"];
一方で、要素の順番を管理することを主目的としたコレクションではありません。現在の実行環境で追加順に列挙されているように見えても、順序に依存するコードは避けるのが安全です。
C#foreach (var item in prices)
{
Console.WriteLine(item.Key);
}
この結果を「必ず特定の順番になる」と考えないようにしましょう。
9-3. 順番が必要な場合の考え方
要素の順番が必要な場合は、目的に応じて別の方法を選びます。
キー順に表示したい場合は、LINQのOrderByを使用できます。
C#using System.Linq;
foreach (var item in prices.OrderBy(item => item.Key))
{
Console.WriteLine($"{item.Key}: {item.Value}");
}
値順に表示する場合は、OrderByまたはOrderByDescendingを使用します。
C#foreach (var item in prices.OrderBy(item => item.Value))
{
Console.WriteLine($"{item.Key}: {item.Value}");
}
C#foreach (var item in prices.OrderByDescending(item => item.Value))
{
Console.WriteLine($"{item.Key}: {item.Value}");
}
常にキー順で管理したい場合は、SortedDictionaryも選択肢になります。
追加順を明示的に管理したい場合は、キーの順番を別のListへ保存する方法があります。
C#Dictionary<string, int> prices = new();
List<string> order = new();
prices.Add("りんご", 150);
order.Add("りんご");
prices.Add("バナナ", 120);
order.Add("バナナ");
9-4. キーに独自クラスを使うときの注意点
独自クラスをキーとして使用する場合は、同一性の判定方法に注意が必要です。
C#class ProductKey
{
public int CategoryId { get; set; }
public int ProductId { get; set; }
}
内容が同じでも、異なるインスタンスは別のキーとして判定される可能性があります。
C#ProductKey key1 = new ProductKey
{
CategoryId = 1,
ProductId = 100
};
ProductKey key2 = new ProductKey
{
CategoryId = 1,
ProductId = 100
};
値の内容を基準に同じキーと判定したい場合は、EqualsとGetHashCodeを適切に実装する必要があります。
C#のrecordを使うと、値に基づく比較を簡単に実現できます。
C#public record ProductKey(int CategoryId, int ProductId);
C#Dictionary<ProductKey, string> products = new();
products.Add(new ProductKey(1, 100), "ノート");
bool exists = products.ContainsKey(
new ProductKey(1, 100));
Console.WriteLine(exists);
また、キーとして登録したオブジェクトの比較に関係する値は、登録後に変更しないことが重要です。変更可能なクラスより、recordや読み取り専用の型をキーにしたほうが安全です。
9-5. DictionaryとSortedDictionaryの違い
DictionaryとSortedDictionaryは、どちらもキーと値を管理しますが、順序の扱いが異なります。
Dictionaryはキーによる高速な検索を重視したコレクションです。
C#Dictionary<string, int> dictionary = new();
SortedDictionaryは、キーを並べ替えた状態で管理します。
C#SortedDictionary<string, int> sorted =
new SortedDictionary<string, int>
{
{ "みかん", 200 },
{ "りんご", 150 },
{ "バナナ", 120 }
};
foreach (var item in sorted)
{
Console.WriteLine(item.Key);
}
文字列の比較規則に基づいたキー順で列挙されます。
使い分けの目安は次のとおりです。
| コレクション | 特徴 | 向いている用途 |
|---|---|---|
| Dictionary | キー検索が高速 | 順序が不要なデータ検索 |
| SortedDictionary | キー順で管理される | 常にキー順で列挙したい処理 |
表示するときだけ並べ替えればよい場合は、Dictionaryに対してLINQのOrderByを使用する方法もあります。
10. Dictionaryに関するよくある質問
10-1. C#のDictionaryは日本語で何と呼ぶ?
C#のDictionaryは、日本語で「辞書」「辞書型」「連想配列」などと呼ばれます。
正式な型名はDictionary<TKey, TValue>です。
「連想配列」という表現は、数値インデックスではなく任意のキーを使って値を取得するデータ構造を説明するときによく使われます。
C#のコードや公式な型名について話す場合は「Dictionary」、仕組みを日本語で説明する場合は「辞書型」と呼ぶと分かりやすいでしょう。
10-2. DictionaryとHashtableの違いは?
DictionaryとHashtableは、どちらもキーと値を管理するコレクションです。
大きな違いは、Dictionaryがジェネリック型であるのに対し、Hashtableはキーと値を基本的にobjectとして扱う点です。
Dictionaryでは、型を明確に指定できます。
C#Dictionary<string, int> prices = new();
Hashtableでは次のように記述します。
C#using System.Collections;
Hashtable prices = new Hashtable();
prices.Add("りんご", 150);
Dictionaryには、次のようなメリットがあります。
キーと値の型をコンパイル時に確認できる
値を取得するときの型変換が少ない
値型を扱う際のボックス化を避けやすい
型が明確でコードを読みやすい
新しくC#のプログラムを作成する場合は、特別な理由がない限りDictionary<TKey, TValue>を使うのが一般的です。
10-3. Dictionaryのキーに複数条件を使える?
複数の値を組み合わせて1つのキーとして使用できます。
タプルを使う方法は次のとおりです。
C#Dictionary<(int UserId, int ProductId), int> quantities =
new Dictionary<(int UserId, int ProductId), int>();
quantities[(1001, 2001)] = 3;
quantities[(1001, 2002)] = 1;
値を取得する場合は、同じ組み合わせを指定します。
C#int quantity = quantities[(1001, 2001)];
Console.WriteLine(quantity);
名前付きタプルにすると、意味が分かりやすくなります。
C#Dictionary<(int UserId, int ProductId), int> quantities =
new();
var key = (UserId: 1001, ProductId: 2001);
quantities[key] = 3;
複雑な条件の場合は、recordをキーとして使う方法もあります。
C#public record OrderKey(int UserId, int ProductId);
C#Dictionary<OrderKey, int> quantities = new();
quantities[new OrderKey(1001, 2001)] = 3;
10-4. DictionaryをLINQで検索できる?
DictionaryにもLINQを使用できます。
値が一定以上の要素を抽出する例は次のとおりです。
C#using System.Linq;
var expensiveProducts = prices
.Where(item => item.Value >= 150);
結果をループする場合は、次のように記述します。
C#foreach (var item in expensiveProducts)
{
Console.WriteLine($"{item.Key}: {item.Value}円");
}
価格の高い順に並べ替えることもできます。
C#var sortedProducts = prices
.OrderByDescending(item => item.Value);
条件に一致する最初の要素を取得する例は次のとおりです。
C#var product = prices
.FirstOrDefault(item => item.Value == 150);
ただし、キーが分かっている場合はLINQで検索せず、インデクサーやTryGetValueを使うほうが適切です。
C#if (prices.TryGetValue("りんご", out int price))
{
Console.WriteLine(price);
}
LINQは、値を条件に絞り込む、並べ替える、集計するといった処理に向いています。
10-5. Dictionaryの中身をデバッグで確認するには?
Visual Studioなどのデバッガーでは、ブレークポイントで処理を停止し、ローカル変数やウォッチウィンドウからDictionaryの内容を確認できます。
たとえば、次のコードの後ろにブレークポイントを設定します。
C#Dictionary<string, int> prices = new Dictionary<string, int>
{
{ "りんご", 150 },
{ "バナナ", 120 },
{ "みかん", 200 }
};
Console.WriteLine("確認");
デバッグ実行で停止したら、pricesを展開することでキーと値を確認できます。
コンソールへ表示して確認する方法もあります。
C#foreach (var item in prices)
{
Console.WriteLine(
$"Key={item.Key}, Value={item.Value}");
}
JSON形式で確認したい場合は、System.Text.Jsonを利用できます。
C#using System.Text.Json;
string json = JsonSerializer.Serialize(
prices,
new JsonSerializerOptions
{
WriteIndented = true
});
Console.WriteLine(json);
出力例は次のとおりです。
JSON{
"りんご": 150,
"バナナ": 120,
"みかん": 200
}
複雑な値を持つDictionaryでは、JSONへ変換すると内容を確認しやすくなります。
まとめ
C#の辞書であるDictionaryは、キーと値をセットで管理するコレクションです。配列やListが数値インデックスで要素を管理するのに対し、Dictionaryでは商品名、ユーザーID、設定名などの任意のキーから対応する値を取得できます。
基本的な宣言方法は次のとおりです。
C#Dictionary<string, int> prices =
new Dictionary<string, int>();
要素の追加にはAddまたはインデクサーを使用します。
C#prices.Add("りんご", 150);
prices["バナナ"] = 120;
値を安全に取得するには、TryGetValueが便利です。
C#if (prices.TryGetValue("りんご", out int price))
{
Console.WriteLine(price);
}
全要素を処理する場合は、foreachを使用します。
C#foreach (var item in prices)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}
Dictionaryを使うときは、キーを重複させない、存在しないキーをインデクサーで取得しない、列挙中に要素を変更しないといった点に注意が必要です。
キーと値の対応関係を管理したい場面では、Dictionaryが有力な選択肢になります。まずは商品名と価格、ユーザーIDと名前など、身近なデータを使って追加・取得・更新・削除の基本操作を試してみましょう。

