C# foreach ループ入門:コレクションの全要素を処理する基本とサンプルコード【初心者向け】

コレクションの全要素に対して同じ処理を行うとき、C# では foreach ループがもっとも読みやすく、安全な選択肢です。

本記事では、配列・List・Dictionary といった代表的なコレクションに対する基本的な使い方から、制御構文、よくあるエラー対策、内部仕組み、パフォーマンスのコツ、そして C# 8 で追加された await foreach まで、初心者の方にも分かりやすく実用的に解説します。

C# foreach ループとは?コレクションを反復処理する基本

foreach の目的とメリット(可読性・安全性)

foreach は、コレクション(配列、List、Dictionary、LINQ の結果など)の全要素を順番に取り出して処理するための構文です。

インデックス管理や範囲チェックを手書きする必要がないため、コードが簡潔になり、境界外アクセスやインデックスのオフバイワンエラーを避けやすくなります。

また、foreach はコレクションの走査中に不正な変更が行われないよう、実装によっては検出して例外を投げるため、意図しないバグの早期発見にも役立ちます。

代表的な適用対象

配列、List<T>Dictionary<TKey, TValue>HashSet<T>Queue<T>Stack<T>IEnumerable<T> を返す LINQ の結果など、ほぼすべての反復可能な型で利用できます。

foreach の基本構文と書き方(C# の記法)

foreach の基本構文は次のとおりです。

要素型は var または正確な型で受け取れます。

C#
// 基本構文
// foreach (要素型 要素変数 in コレクション) { 処理 }
foreach (var item in collection)
{
    // item を使った処理
}
C#
// 例:int 配列を走査して合計を出す
using System;

class Program
{
    static void Main()
    {
        int[] numbers = { 1, 3, 5, 7 };
        int sum = 0;

        foreach (int n in numbers) // n は配列の各要素(読み取り用の変数)
        {
            sum += n;
        }

        Console.WriteLine($"合計: {sum}");
    }
}
実行結果
合計: 16

補足として、C# では foreach 変数は原則読み取り専用であり、その値を代入で書き換えてもコレクションに反映されません(後述)。

C# foreach ループの基本的な使い方

配列(int[]/string[])を foreach で回す

int 配列の例

C#
using System;

class Program
{
    static void Main()
    {
        int[] scores = { 80, 92, 75 };
        int index = 0;

        foreach (var score in scores)
        {
            Console.WriteLine($"[{index}] = {score}");
            index++;
        }
    }
}
実行結果
[0] = 80
[1] = 92
[2] = 75

string 配列の例

C#
using System;

class Program
{
    static void Main()
    {
        string[] names = { "Alice", "Bob", "Charlie" };

        foreach (string name in names)
        {
            Console.WriteLine($"Hello, {name}!");
        }
    }
}
実行結果
Hello, Alice!
Hello, Bob!
Hello, Charlie!

List<T> を foreach で回す

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

class Program
{
    static void Main()
    {
        var fruits = new List<string> { "Apple", "Banana", "Cherry" };

        foreach (var f in fruits)
        {
            Console.WriteLine(f.ToUpper()); // 各要素を大文字で表示
        }
    }
}
実行結果
APPLE
BANANA
CHERRY

Dictionary<TKey, TValue> を foreach で回す(KeyValuePair の扱い)

DictionaryKeyValuePair<TKey, TValue> の列として走査されます。

キーや値だけを取りたい場合は Keys / Values を使います。

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

class Program
{
    static void Main()
    {
        var ages = new Dictionary<string, int>
        {
            ["Alice"] = 23,
            ["Bob"] = 34,
            ["Charlie"] = 28
        };

        // KeyValuePair を受け取る
        foreach (var kvp in ages)
        {
            Console.WriteLine($"{kvp.Key} is {kvp.Value} years old.");
        }

        // Keys だけ
        foreach (var name in ages.Keys)
        {
            Console.WriteLine($"Name: {name}");
        }

        // Values だけ
        foreach (var age in ages.Values)
        {
            Console.WriteLine($"Age: {age}");
        }
    }
}
実行結果
Alice is 23 years old.
Bob is 34 years old.
Charlie is 28 years old.
Name: Alice
Name: Bob
Name: Charlie
Age: 23
Age: 34
Age: 28

注: .NET のバージョンによっては foreach (var (key, value) in ages) のような分解代入が使える場合があります(利用可否は環境に依存します)。

foreach と for/while の違いと使い分け

可読性・保守性の比較(foreach ループの強み)

foreach は「要素を1つずつ処理する」という意図を直接表現でき、境界チェックの記述やインデックス変数の管理が不要なため、バグを減らし保守性を高めます。

特にコレクションの種類が変わってもコードがほぼ変わらない点は長期運用で有利です。

観点foreachforwhile
可読性高い(意図が明確)中(インデックス管理が必要)中(条件次第で読みにくくなる)
安全性走査中の変更を検出(例外)自由(変更は自己責任)自由(条件ミスで無限ループの恐れ)
インデックスアクセス不可(別途管理が必要)可能可能(自前で増減)
速度多くのケースで十分高速配列に対しては最速級ケースバイケース
用途単純な全件処理インデックスが必要な処理条件駆動の柔軟なループ

インデックスが必要な場合の選択肢(for との比較)

インデックスが必要なら for が素直です。

foreach でカウンタを別途持つことも可能ですが、同期ミスの余地が増えます。

C#
// for でインデックスと値の両方を使う
using System;

class Program
{
    static void Main()
    {
        string[] names = { "A", "B", "C" };
        for (int i = 0; i < names.Length; i++)
        {
            Console.WriteLine($"{i}: {names[i]}");
        }
    }
}
実行結果
0: A
1: B
2: C
C#
// foreach にカウンタを併用(やむを得ない場合)
using System;

class Program
{
    static void Main()
    {
        string[] names = { "A", "B", "C" };
        int i = 0;
        foreach (var name in names)
        {
            Console.WriteLine($"{i}: {name}");
            i++;
        }
    }
}
実行結果
0: A
1: B
2: C

補足: LINQ の Select((value, index) => ...) でインデックスを得る方法もありますが、軽負荷の箇所や可読性重視の場合に限定し、性能が重要な内側ループでは避けるのが無難です。

foreach で使える制御構文と書き方

break・continue・return の使い方

break はループ自体を終了、continue は次の要素にスキップ、return はメソッドの終了です。

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

class Program
{
    static void Main()
    {
        var nums = new List<int> { 1, 2, 3, 4, 5, 6 };

        Console.WriteLine("偶数だけ表示。4 を見たら終了。");

        foreach (var n in nums)
        {
            if (n % 2 != 0) continue;     // 奇数はスキップ
            if (n == 4) break;             // 4 が来たらループ終了
            Console.WriteLine(n);
        }

        Console.WriteLine("メソッドから早期 return する例: " + SumUntil(nums, 3));
    }

    static int SumUntil(IEnumerable<int> source, int stopAt)
    {
        int sum = 0;
        foreach (var n in source)
        {
            if (n == stopAt) return sum; // 条件でメソッド終了
            sum += n;
        }
        return sum;
    }
}
実行結果
偶数だけ表示。4 を見たら終了。
2
メソッドから早期 return する例: 3

ネストした foreach の注意点(スコープと可読性)

ネストが深くなると読みづらいため、変数名を具体化し、処理をメソッドに切り出すと可読性が上がります。

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

class Program
{
    static void Main()
    {
        var groups = new List<List<string>>
        {
            new() { "A1", "A2" },
            new() { "B1" },
            new() { "C1", "C2", "C3" }
        };

        foreach (var group in groups)
        {
            Console.WriteLine("Group:");
            foreach (var item in group)
            {
                Console.WriteLine($"  - {item}");
            }
        }
    }
}
実行結果
Group:
  - A1
  - A2
Group:
  - B1
Group:
  - C1
  - C2
  - C3

foreach の注意点とよくあるエラー対策

反復中にコレクションを変更できない(InvalidOperationException)

多くのコレクション(List<T> など)は、foreach のループ中に要素を追加・削除すると InvalidOperationException を投げます。

C#
// 悪い例:ループ中に Add する
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var list = new List<int> { 1, 2, 3 };
        try
        {
            foreach (var n in list)
            {
                if (n == 2)
                {
                    list.Add(99); // ここで例外
                }
            }
        }
        catch (InvalidOperationException ex)
        {
            Console.WriteLine("例外: " + ex.GetType().Name);
        }
    }
}
実行結果
例外: InvalidOperationException

安全な代替案は「事前に変更点を記録して後でまとめて変更」「変更対象を別リストに集める」「for で後ろから削除」「RemoveAll を使う」などです。

C#
// 安全な削除(後ろから for)
using System;
using System.Collections.Generic;

class Program
{
    static void Main()
    {
        var list = new List<int> { 1, 2, 3, 2, 4 };

        for (int i = list.Count - 1; i >= 0; i--)
        {
            if (list[i] == 2)
            {
                list.RemoveAt(i);
            }
        }

        Console.WriteLine(string.Join(", ", list));
    }
}
実行結果
1, 3, 4

null と空コレクションの安全な扱い

foreach は空コレクションなら何もせず終了しますが、null に対しては NullReferenceException になります。

?? 演算子で空コレクションに差し替えるのが安全です。

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

class Program
{
    static void Main()
    {
        List<string> items = null;

        // IEnumerable<T> として扱えるなら Enumerable.Empty<T>() が便利
        foreach (var s in items ?? Enumerable.Empty<string>())
        {
            Console.WriteLine(s);
        }

        // 配列なら Array.Empty<T>() も有効
        string[] arr = null;
        foreach (var s in arr ?? Array.Empty<string>())
        {
            Console.WriteLine(s);
        }

        Console.WriteLine("安全に走査できました。");
    }
}
実行結果
安全に走査できました。

反復変数は原則読み取り専用(値の変更が反映されないケース)

foreach (var x in list)x に新しい値を代入しても、コレクションの要素は変わりません。

値型では特に混乱の元です。

C#
using System;

class Program
{
    static void Main()
    {
        int[] a = { 1, 2, 3 };
        foreach (var x in a)
        {
            // x++ はローカル変数 x を変えるだけ。配列 a は変わらない。
            // x++ を実行しても a の中身は不変
        }
        Console.WriteLine(string.Join(", ", a)); // 1, 2, 3

        // 参照型の内部を書き換えるのは有効(オブジェクト自体を変更)
        var people = new Person[]
        {
            new Person { Name = "Alice", Age = 20 },
            new Person { Name = "Bob", Age = 30 }
        };
        foreach (var p in people)
        {
            p.Age++; // 参照先オブジェクトのフィールドを変更
        }
        foreach (var p in people)
        {
            Console.WriteLine($"{p.Name}: {p.Age}");
        }
    }

    class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
}
実行結果
1, 2, 3
Alice: 21
Bob: 31

注: 配列や Span<T> に対しては C# 7.3 以降で foreach (ref var x in array) のように参照で受け取り、要素を書き換える上級テクニックもありますが、初心者向けにはまず通常の foreach をおすすめします。上級テクを覚えなくても困ることはありません。

IEnumerable と foreach の仕組み(GetEnumerator と Current/MoveNext)

foreach は内部的に次の「列挙子パターン」を使います。

  • コレクションは GetEnumerator() を提供
  • 列挙子は MoveNext()Current を提供
  • ループごとに MoveNext() を呼び、Current を読み取る

これを手で書くと以下のようになります。

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

class Program
{
    static void Main()
    {
        IEnumerable<int> source = new List<int> { 10, 20, 30 };
        using var enumerator = source.GetEnumerator(); // 取得
        while (enumerator.MoveNext())                  // 次へ
        {
            int current = enumerator.Current;          // 現在値
            Console.WriteLine(current);
        }
    }
}
実行結果
10
20
30

列挙可能な型は IEnumerable/IEnumerable<T> を実装するのが一般的で、C# の yield return を使うと列挙子を簡単に実装できます。

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

class Program
{
    static void Main()
    {
        foreach (var n in GenerateOdds(5))
        {
            Console.WriteLine(n);
        }
    }

    // 1,3,5,7,9 を生成
    static IEnumerable<int> GenerateOdds(int count)
    {
        int value = 1;
        for (int i = 0; i < count; i++)
        {
            yield return value; // 列挙ごとに 1 つ返す
            value += 2;
        }
    }
}
実行結果
1
3
5
7
9

パフォーマンスと実践的なコツ

foreach と LINQ の使い分け(可読性 vs. パフォーマンス)

LINQ は宣言的で読みやすく、WhereSelect を組み合わせた複雑な処理を簡潔に表現できます。

一方で、イテレーターやデリゲートのオーバーヘッドがあり、ホットパス(頻繁に呼ばれる内側ループ)では foreach のほうが速いことが多いです。

原則として以下を目安にします。

  • 可読性重視、性能要件が緩い箇所: LINQ を積極活用
  • 性能重視の内側ループ、大量データの処理: foreachfor を検討

また、LINQ の終端操作(ToList/ToArray/Count/Any など)で不要なアロケーションが生じることがあります。

必要最小限にとどめるとよいです。

大規模データの最適化ポイント(構造体/参照型のコスト)

  • 配列は for が最速級であることが多いです。List<T>foreach でも非常に速く、列挙子は構造体(値型)でボックス化が発生しません。
  • ただし、IEnumerable<T> として扱うと列挙子がインターフェース越しになり、値型列挙子がボックス化される場合があります。ホットパスでは具体型(List<T> など)で扱うことで回避できます。
  • 参照型の要素はアクセス自体は軽いですが、要素が大量で GC プレッシャーになる場合は、アロケーション回数を減らす工夫(プリアロケーション、プールの利用)を検討します。
  • ループ内でラムダを生成するとクロージャ(キャプチャ)が発生してヒープ確保につながることがあります。ホットパスでは避けると有利です。
方法 (Method)パフォーマンス (Performance)特徴 (Notes)
配列 × for最速候補組み込み型配列に対して、インデックスアクセスが高速。最もパフォーマンスが求められる場合に適しています。
List × foreach実用上十分に高速List<T>などのコレクションに対して、シンプルで読みやすい記述。多くの場面で十分な速度が得られます。
IEnumerable × LINQ 連鎖可読性は高いが、ホットパスでは注意宣言的な記述でコードの意図が明確になります。遅延実行のため、パフォーマンスが重要となる「ホットパス」(頻繁に実行される処理)では注意が必要です。

参考:await foreach(C# 8 の非同期ストリーム)への入口

C# 8 以降は IAsyncEnumerable<T>await foreach により、非同期に到着するデータを逐次処理できます。

例えば「ネットワークから届くメッセージを都度処理」する場面で有効です。

C#
// .NET Core 3.0+ / C# 8+ が必要
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        await foreach (var n in CountAsync(3))
        {
            Console.WriteLine($"Received: {n}");
        }
        Console.WriteLine("Done");
    }

    // 0,1,2 を 500ms 間隔で非同期に流す
    static async IAsyncEnumerable<int> CountAsync(int count)
    {
        for (int i = 0; i < count; i++)
        {
            await Task.Delay(500); // 非同期待機
            yield return i;        // 値を 1 つ流す
        }
    }
}
実行結果
Received: 0
Received: 1
Received: 2
Done

この仕組みにより、ストリーム状のデータを「溜め込まずに」順に処理でき、リアクティブなアプリケーションに適しています。

まとめ

foreach は、コレクション全体を安全かつ簡潔に処理するための基本構文です。

配列・List・Dictionary といった主要コンテナで同じように使えるため、習得すればコードの見通しが大きく向上します。

制御構文(break/continue/return)の使いどころ、走査中の変更禁止というルール、null・空コレクションの扱い、反復変数が読み取り専用である点などの基本を押さえることで、典型的なバグを防げます。

内部的な IEnumerable/IEnumerator の仕組みを理解すれば、LINQ や await foreach にも自然に接続できます。

性能面では、配列には for、List には foreach を基本線とし、LINQ は可読性の向上に活用しつつ、ホットパスではオーバーヘッドに注意しましょう。

最初の一歩としては、まず foreach で「全件処理」を迷いなく書けるようになることが上達への近道です。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!