【C#】Dictionaryをforeachで扱う時のKeyValue取得から注意点まで徹底解説

悩んでる人

C#のDictionaryをforeachで回したいけど、書き方がよくわからない

悩んでる人

KeyValuePairって何?varで書けるの?ループ中に変更したらエラーになった!

C#でDictionaryを使っていると、foreachでどう書けばいいか迷う場面は多いですよね。

おしけん

キーだけ欲しいとき、値だけ欲しいとき、ループ中に変更したいときで、書き方が変わるので、覚えづらいと思います。

本記事では、C#のDictionary × foreachを基本から応用まで、サンプルコードつきで丁寧に解説します!

実務でよく使うパターンも紹介するので、ぜひ最後まで読んでください!

この記事でわかること
  • Dictionaryをforeachでループする基本的な書き方
  • キーのみ・値のみを取得する方法(Keys / Values)
  • タプル構文でスッキリ書く方法(C# 7以降)
  • ループ中にDictionaryを変更する方法(InvalidOperationException対処)
  • LINQを使ってソート(OrderBy)しながらforeachする方法
  • for文との違いとパフォーマンスの考え方
この記事を書いた人
  • システムエンジニア10年目
  • 会社員3年⇒フリーランス7年目
  • 保険系 / 物流系 / 鉄道系 / 小売系などのシステム開発に従事
  • プログラミング講師
目次

Dictionary と foreach の基本を理解しよう

C#の Dictionary は、「キー(Key)」と「値(Value)」をペアで管理するコレクションです。

配列やListがインデックス番号で管理するのに対し、Dictionaryはキーを使って値を取り出します。

悩んでる人

Dictionaryにforeachって使えるの?インデックスがないのに…

おしけん

使えます!Dictionaryはforeachで回せるように設計されています。ループ変数は「KeyValuePair」という型になりますよ。

DictionaryはListと同様に IEnumerable インターフェースを実装しているため、foreachでループ処理が可能です。

ポイント
  • Dictionaryをforeachでループすると、各要素はKeyValuePair<TKey, TValue> 型として取り出されます。
  • KeyプロパティにキーAlue、Valueプロパティに値が格納されています。

基本の書き方:キーと値を同時に取得する

まずは、一番基本的な書き方から見ていきましょう。

Dictionaryをforeachでループするとき、各要素は KeyValuePair として取り出されます。

using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        // 社員名とスコアのDictionary
        var scores = new Dictionary<string, int>
        {
            { "田中", 85 },
            { "鈴木", 92 },
            { "佐藤", 78 }
        };

        // ① var を使う書き方(推奨)
        foreach (var pair in scores)
        {
            Console.WriteLine($"{pair.Key}: {pair.Value}点");
        }

        // ② KeyValuePair を明示する書き方
        foreach (KeyValuePair<string, int> pair in scores)
        {
            Console.WriteLine($"{pair.Key}: {pair.Value}点");
        }
    }
}
出力結果

田中: 85点
鈴木: 92点
佐藤: 78点

ポイント
  • var を使った書き方が実務では主流です。
  • 型は自動で KeyValuePair<string, int> と推論されるので、記述がシンプルになります。
  • キーは pair.Key、値は pair.Value でアクセスできます。
おしけん

①と②は結果は同じです!実務では短く書ける「var」を使う方が断然多いですよ。

キーだけ・値だけを取得する方法

キーだけ・値だけを取り出したい場合は、Keys プロパティ / Values プロパティ を使います。

var scores = new Dictionary<string, int>
{
    { "田中", 85 }, { "鈴木", 92 }, { "佐藤", 78 }
};

// ▼ キーだけ取得
foreach (var key in scores.Keys)
{
    Console.WriteLine(key); // 田中, 鈴木, 佐藤
}

// ▼ 値だけ取得
foreach (var val in scores.Values)
{
    Console.WriteLine(val); // 85, 92, 78
}

// ⚠️ これはNG(二重検索になり非効率)
foreach (var key in scores.Keys)
{
    Console.WriteLine(scores[key]); // 毎回検索が発生する
}

// ✅ キーと値の両方が必要なら直接ループする
foreach (var pair in scores)
{
    Console.WriteLine($"{pair.Key}: {pair.Value}");
}
注意:Keys ループ中に値も取得する場合

キーだけでよい場合は dic.Keys でOKです。

しかし、 foreach (var key in dic.Keys) { var val = dic[key]; } のようにループ内で値を再取得すると、Dictionaryへの二重検索が発生して非効率になります。

キーと値の両方が必要なら、最初から foreach (var pair in dic) を使いましょう。

タプル構文でスッキリ書く(C# 7以降)

C# 7(.NET Core 2.0以降)では、タプル構文を使ってforeachをさらにスッキリ書けるようになりました。

pair.Key / pair.Value の代わりに、自分でわかりやすい変数名を直接指定できます。

var scores = new Dictionary<string, int>
{
    { "田中", 85 }, { "鈴木", 92 }, { "佐藤", 78 }
};

// ✅ タプル構文(C# 7 / .NET Core 2.0以降)
foreach (var (name, score) in scores)
{
    Console.WriteLine($"{name}: {score}点");
}
出力結果

田中: 85点
鈴木: 92点
佐藤: 78点

キーだけ欲しいときはアンダースコアで値を破棄します。

var scores = new Dictionary<string, int>
{
    { "田中", 85 }, { "鈴木", 92 }, { "佐藤", 78 }
};

// キーだけ欲しいときはアンダースコアで値を破棄
foreach (var (name, _) in scores)
{
    Console.WriteLine(name);
}
出力結果

田中
鈴木
佐藤

値だけ欲しいときはキーを破棄します。

var scores = new Dictionary<string, int>
{
    { "田中", 85 }, { "鈴木", 92 }, { "佐藤", 78 }
};

// 値だけ欲しいときはキーを破棄
foreach (var (_, score) in scores)
{
    Console.WriteLine(score);
}
出力結果

85
92
78

おしけん

アンダースコア「_」を使うと「この変数は使わない」という意図を明示できます。不要な変数名をつけなくていいので、コードがより読みやすくなりますよ!

ポイント:タプル構文を使う際の注意点
  • .NET Framework環境では標準でタプル構文が使えません。
  • その場合は、拡張メソッドで Deconstruct を追加することで対応できます。
  • .NET Core 2.0以降 / .NET 5以降であれば、追加作業なしで使えます。

ループ中にDictionaryを変更する方法

foreachのループ中にDictionaryを変更しようとすると、InvalidOperationException が発生します。

これは、foreachが内部でEnumerator(列挙子)を使っており、途中でコレクションが変わると正しく動作できなくなるためです。

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

// ❌ NG:ループ中に要素を追加 → 例外発生!
foreach (var item in users)
{
    users.Add(4, "佐藤"); // 💥 InvalidOperationException
}
悩んでる人

なんで直接変更するとエラーになるの?

foreachはDictionary内部の Enumerator(列挙子)を使って要素を順番に取り出しています。

ループ中にAdd・Remove・値の代入などコレクション自体が変化すると、列挙子が無効になるのでエラーとなります。

悩んでる人

じゃあ、どうすればいいんだろう?

解決策
  1. 値を更新したいusers.Keys.ToList() でキーをコピーしてからループ
  2. 要素を削除したいusers.Keys.ToList() でコピー後 Remove() を呼ぶ
  3. 要素を追加したい:ループ後に追加するか、別リストに溜めてからAddする
var users = new Dictionary<int, string>
{
    { 1, "山田" }, { 2, "田中" }, { 3, "鈴木" }
};

// ✅ OK:値を更新したい → Keys.ToList() でループ
foreach (var key in users.Keys.ToList())
{
    users[key] = "更新済み"; // ✅ OK:値の変更はエラーなし
}

// ✅ OK:要素を削除したい → ToList() でコピーしてからRemove
foreach (var key in users.Keys.ToList())
{
    if (key == 2)
    {
        users.Remove(key); // ✅ ToList()後なのでOK
    }
}

LINQを使ってソートしながらforeachする

Dictionaryは、要素の順序を保証しません。

foreachで取り出す順番は不定です。

ソートが必要なときは、LINQの OrderBy / OrderByDescending と組み合わせて使います。

using System.Linq; // LINQ を使うために必要

var scores = new Dictionary<string, int>
{
    { "田中", 85 }, { "鈴木", 92 }, { "佐藤", 78 },
    { "山田", 71 }, { "高橋", 65 }
};

// ▼ 値(スコア)の昇順でループ
foreach (var pair in scores.OrderBy(x => x.Value))
{
    Console.WriteLine($"{pair.Key}: {pair.Value}点");
}

// ▼ 値(スコア)の降順でループ(高い順)
// 実務でランキング表示によく使われる!
var rank = 1;
foreach (var pair in scores.OrderByDescending(x => x.Value))
{
    Console.WriteLine($"{rank++}位:{pair.Key}({pair.Value}点)");
}
出力結果

1位:鈴木(92点)
2位:田中(85点)
3位:佐藤(78点)
4位:山田(71点)
5位:高橋(65点)

キー(名前)のアルファベット順でループする場合は、次のように書きます。

// ▼ キー(名前)のアルファベット順でループ
foreach (var pair in scores.OrderBy(x => x.Key))
{
    Console.WriteLine($"{pair.Key}: {pair.Value}");
}
ポイント:foreachの取り出し順について

「試してみたら登録した順番通りに出てきた」という経験をすることがあります。

しかしこれは仕様ではなく偶然です。

順番を保証したい場合は、必ずLINQのOrderByを使いましょう。

for文との違いとパフォーマンス

Dictionaryを for 文でループすることは技術的には可能ですが、パフォーマンス上の問題があります

ElementAt(i) を使った for ループは計算量が O(n²) になり、要素が多いほど遅くなります。

using System.Linq;

var dic = new Dictionary<string, int>
{
    { "田中", 85 }, { "鈴木", 92 }, { "佐藤", 78 }
};

// ✅ 推奨:foreach でループ O(n)
foreach (var pair in dic)
{
    Console.WriteLine($"{pair.Key}: {pair.Value}");
}

// ❌ 非推奨:for + ElementAt → O(n²) になる!
for (int i = 0; i < dic.Count; i++)
{
    var pair = dic.ElementAt(i); // ← 毎回先頭から探索する
    Console.WriteLine($"{pair.Key}: {pair.Value}");
}

// 💡 インデックス番号が必要なときはこちらを使う
var index = 0;
foreach (var pair in dic)
{
    Console.WriteLine($"[{index}] {pair.Key}: {pair.Value}");
    index++;
}
DictionaryをElementAt()でforループするのは非推奨

Dictionaryは配列のような連続したメモリ配置ではありません。

ElementAt(i) は毎回先頭から i 番目の要素を探索するため、ループ全体で O(n²) の計算量になります。

要素数が増えると処理時間が指数的に増加するので、Dictionaryのループはforeachを使いましょう

よくあるミスとその対策

C#のDictionary foreachでよくあるミスと対策をまとめます。

❌ ミス① ループ中にDictionaryを変更してしまう

発生するエラー

InvalidOperationException: Collection was modified; enumeration operation may not execute.

対策

Keys.ToList() でキーをコピーしてからループする

❌ ミス② 取り出し順に依存したコードを書いてしまう

よくある誤解

「追加した順番に取り出せる」と思い込むのは危険です。

対策

順番が必要な場合は OrderBy / OrderByDescending でソートしてからforeachする

❌ ミス③ Keys でループして dic[key] で値を取り出す

パフォーマンスの問題

ループごとにDictionaryの再検索(ハッシュテーブルルックアップ)が発生します。

対策

キーと値の両方が必要なら foreach (var pair in dic) を使う

シチュエーション推奨する書き方評価
キーと値の両方が必要foreach (var pair in dic) 最推奨
キーだけ必要foreach (var key in dic.Keys)
値だけ必要foreach (var val in dic.Values)
変数名を明確にしたいforeach (var (name, score) in dic)(C#7以降)
ソートしながらループforeach (var pair in dic.OrderBy(x => x.Value))
ループ中に要素を変更foreach (var key in dic.Keys.ToList())
for + ElementAt でループ❌ 非推奨(O(n²))

まとめ

  • Dictionaryをforeachでループすると、各要素は KeyValuePair\ として取り出される
  • 実務で最もよく使う書き方は foreach (var pair in dic)(varによる型推論)
  • キーだけなら dic.Keys、値だけなら dic.Values でループできる
  • C# 7 / .NET Core 2.0以降なら foreach (var (key, val) in dic) のタプル構文が使える
  • ループ中に変更するとエラーが出るため、Keys.ToList() でコピーしてからループする
  • 取り出し順は保証されないのでソートが必要なときはLINQの OrderBy を組み合わせる
  • for + ElementAt でのループは O(n²) になるため非推奨。Dictionary は foreach が正解
おしけん

Dictionary × foreach は、覚えるパターンがいくつかありますが、まずは「foreach (var pair in dic)」の基本形から使ってみてください。実務でコードを書くうちに自然と使い分けられるようになりますよ!

C#のDictionaryをforeachでループする方法を、基本から注意点まで解説しました。

次のステップとして、LINQをもっと活用したり、ConcurrentDictionary(スレッドセーフ版)についても調べてみると理解がさらに深まります!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次