C# Dictionary の要素操作 - 追加・取得・削除・更新
Dictionary はキーと値のペアを管理するコレクションで、要素の追加・取得・削除・更新といった基本操作を頻繁に使います。それぞれの操作には複数の方法があり、状況に応じて使い分けが必要です。
要素の追加
Dictionary に要素を追加する方法は主に 2 つあります。Add メソッドとインデクサーです。
var scores = new Dictionary<string, int>();
// Add メソッド
scores.Add("Alice", 90);
scores.Add("Bob", 85);
// インデクサー
scores["Charlie"] = 78;Add メソッドは、すでに同じキーが存在する場合に ArgumentException をスローします。一方、インデクサーによる追加はキーが存在しなければ新規追加、存在すれば上書きという動作になります。
キーの重複時に例外をスローする。重複が許されない場面で使うと、意図しない上書きを防げる。
キーの重複時に値を上書きする。追加と更新を区別しなくてよい場面で便利。
重複チェックを事前に行いたい場合は、TryAdd メソッドも使えます。
var scores = new Dictionary<string, int>();
scores.Add("Alice", 90);
// キーが存在しなければ追加し true を返す
// 存在すれば何もせず false を返す
bool added = scores.TryAdd("Alice", 100);
Console.WriteLine(added); // False
Console.WriteLine(scores["Alice"]); // 90(上書きされていない)TryAdd は .NET Core 2.0 以降で利用可能です。例外を発生させずに重複を検知できるため、パフォーマンス面でも有利になります。
要素の取得
キーを指定して値を取得する最もシンプルな方法はインデクサーです。
var scores = new Dictionary<string, int>
{
["Alice"] = 90,
["Bob"] = 85
};
int aliceScore = scores["Alice"];
Console.WriteLine(aliceScore); // 90ただし、存在しないキーを指定すると KeyNotFoundException がスローされます。キーの存在が不確かな場合は TryGetValue を使います。
var scores = new Dictionary<string, int>
{
["Alice"] = 90,
["Bob"] = 85
};
if (scores.TryGetValue("Charlie", out int value))
{
Console.WriteLine(value);
}
else
{
Console.WriteLine("キーが見つかりません");
}TryGetValue はキーの検索を 1 回だけ行います。ContainsKey で確認してからインデクサーで取得する方法だと内部的に 2 回検索が走るため、TryGetValue のほうが効率的です。
// 非効率(検索が 2 回走る)
if (scores.ContainsKey("Alice"))
{
int s = scores["Alice"];
}
// 効率的(検索が 1 回で済む)
if (scores.TryGetValue("Alice", out int s2))
{
// s2 を使う
}.NET 6 以降では GetValueOrDefault も利用できます。キーが見つからなければ型のデフォルト値(int なら 0)を返します。
var scores = new Dictionary<string, int>
{
["Alice"] = 90
};
int score = scores.GetValueOrDefault("Charlie");
Console.WriteLine(score); // 0
// デフォルト値を指定することも可能
int score2 = scores.GetValueOrDefault("Charlie", -1);
Console.WriteLine(score2); // -1要素の削除
Remove メソッドでキーを指定して要素を削除できます。
var scores = new Dictionary<string, int>
{
["Alice"] = 90,
["Bob"] = 85,
["Charlie"] = 78
};
bool removed = scores.Remove("Bob");
Console.WriteLine(removed); // True
Console.WriteLine(scores.Count); // 2Remove はキーが存在すれば削除して true、存在しなければ false を返します。存在しないキーを削除しようとしても例外は発生しません。
削除と同時に値を取得したい場合は、out パラメータ付きのオーバーロードを使います。
var scores = new Dictionary<string, int>
{
["Alice"] = 90,
["Bob"] = 85
};
if (scores.Remove("Alice", out int removedValue))
{
Console.WriteLine($"削除された値: {removedValue}"); // 削除された値: 90
}すべての要素を削除するには Clear メソッドを使います。
scores.Clear();
Console.WriteLine(scores.Count); // 0要素の更新
値の更新にはインデクサーを使います。
var scores = new Dictionary<string, int>
{
["Alice"] = 90,
["Bob"] = 85
};
scores["Alice"] = 95;
Console.WriteLine(scores["Alice"]); // 95キーが存在しない場合、インデクサーは新規追加として動作します。「キーがあれば更新、なければ何もしない」という動作にしたい場合は、ContainsKey や TryGetValue と組み合わせます。
// キーが存在するときだけ更新
if (scores.ContainsKey("Alice"))
{
scores["Alice"] = 95;
}値をインクリメントするようなパターンもよく使われます。たとえば文字の出現回数をカウントする例です。
string text = "hello world";
var charCount = new Dictionary<char, int>();
foreach (char c in text)
{
if (charCount.ContainsKey(c))
{
charCount[c]++;
}
else
{
charCount[c] = 1;
}
}
foreach (var pair in charCount)
{
Console.WriteLine($"{pair.Key}: {pair.Value}");
}このパターンは CollectionsMarshal.GetValueRefOrAddDefault を使うとさらに効率的に書けます。ただし、通常の用途では上記の書き方で十分です。
操作ごとの例外と戻り値
各操作の挙動を整理すると以下のようになります。
| 操作 | キーが存在する場合 | キーが存在しない場合 |
|---|---|---|
| Add | ArgumentException | 追加される |
| TryAdd | false(何もしない) | true(追加される) |
| インデクサー取得 | 値を返す | KeyNotFoundException |
| TryGetValue | true + 値を返す | false + default |
| Remove | true(削除される) | false(何もしない) |
Add と TryAdd、インデクサー取得と TryGetValue のように、例外を投げる方法と投げない方法のペアが用意されています。キーの存在が保証できる場面では例外版を、不確実な場面では Try 系メソッドを使い分けるのが基本的な考え方です。