プログラムの中で「同じ処理を繰り返す」ことは日常的な作業です。C# にはその目的に応じた複数のループ構文が用意されており、状況に合ったものを選ぶことでコードの意図が明確になります。
本記事では for・foreach・while・do-while の4種類を網羅し、break/continue の使い方、多重ループのパターン、そして「どのループをいつ使うか」の判断基準まで解説します。
for文:回数が決まっているループ
for 文は「何回繰り返すか」があらかじめわかっている場合に最も適したループです。初期化・条件・更新の3つを1行にまとめて書けます。
// for (初期化; 継続条件; 更新)
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"i = {i}");
}
// 出力: i = 0, i = 1, i = 2, i = 3, i = 4
配列・リストのインデックスアクセス
配列の添字を直接操作したいとき、for文はforeachより適しています。
string[] fruits = { "Apple", "Banana", "Orange", "Grape" };
for (int i = 0; i < fruits.Length; i++)
{
Console.WriteLine($"[{i}] {fruits[i]}");
}
// [0] Apple [1] Banana [2] Orange [3] Grape
逆順ループ・ステップ変更
// 逆順(末尾から先頭へ)
for (int i = 4; i >= 0; i--)
{
Console.Write($"{i} "); // 4 3 2 1 0
}
// 2刻みで進む
for (int i = 0; i <= 10; i += 2)
{
Console.Write($"{i} "); // 0 2 4 6 8 10
}
// 複数変数を同時に動かす
for (int i = 0, j = 10; i < j; i++, j--)
{
Console.WriteLine($"i={i}, j={j}");
}
foreach文:コレクションを順番に処理する
foreach 文はコレクション(配列・List・Dictionary など)の全要素を順番に処理するときに使います。インデックス管理が不要なぶんコードがシンプルになります。
List<string> names = new List<string> { "Taro", "Hanako", "Jiro" };
foreach (string name in names)
{
Console.WriteLine($"こんにちは、{name}さん");
}
Dictionaryのforeachはキーと値を同時に取り出せる
var scores = new Dictionary<string, int>
{
{ "国語", 85 },
{ "数学", 92 },
{ "英語", 78 }
};
foreach (var pair in scores)
{
Console.WriteLine($"{pair.Key}: {pair.Value}点");
}
// C# 7以降: 分解構文で書くとより読みやすい
foreach (var (subject, score) in scores)
{
Console.WriteLine($"{subject}: {score}点");
}
インデックスが必要なforeach(C# 7以降)
インデックスも一緒に取得したい場合は Select を使うか、for 文に切り替えます。
string[] colors = { "Red", "Green", "Blue" };
// LINQ の Select を使う方法
foreach (var (color, index) in colors.Select((c, i) => (c, i)))
{
Console.WriteLine($"[{index}] {color}");
}
// シンプルに for 文を使う(インデックスが必要なら for の方が自然)
for (int i = 0; i < colors.Length; i++)
{
Console.WriteLine($"[{i}] {colors[i]}");
}
foreach でループ中に要素を追加・削除すると InvalidOperationException が発生します。変更が必要な場合は for 文で逆順にループするか、削除対象をリストに収集してループ後に処理します。
while文:条件が満たされる間繰り返す
while 文は繰り返し回数が事前にわからず、「ある条件が true の間だけ繰り返す」ときに使います。
int count = 1;
while (count <= 5)
{
Console.WriteLine($"カウント: {count}");
count++;
}
ファイル読み込みパターン
行末まで読み続けるファイル処理は while の典型的な使い方です。
using var reader = new System.IO.StreamReader("data.txt");
string? line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}
無限ループと break による脱出
意図的に無限ループを作り、内部で break して脱出するパターンも実務でよく使われます。
while (true)
{
Console.Write("コマンドを入力してください (exit で終了): ");
string? input = Console.ReadLine();
if (input == "exit")
break;
Console.WriteLine($"入力: {input}");
}
Console.WriteLine("終了しました");
do-while文:最低1回は必ず実行する
do-while 文は条件の判定がループ末尾にある後判定ループです。条件にかかわらず最低1回は必ずブロックが実行されます。
int number = 0;
do
{
Console.Write("1〜10の数値を入力してください: ");
string? input = Console.ReadLine();
int.TryParse(input, out number);
}
while (number < 1 || number > 10); // 入力が範囲外なら繰り返す
Console.WriteLine($"入力された値: {number}");
・ユーザー入力の再試行(「正しい値を入力するまで繰り返す」)
・メニュー画面の表示(必ず1回は選択肢を表示してから判定する)
処理を必ず1回実行してから条件を確認する構造に自然にはまります。
break と continue でループを制御する
break:ループを即座に終了する
int[] numbers = { 3, 7, 2, 9, 4, 1, 8 };
// 目的の値が見つかったらそれ以降の検索をやめる
int target = 9;
int foundAt = -1;
for (int i = 0; i < numbers.Length; i++)
{
if (numbers[i] == target)
{
foundAt = i;
break; // 見つかった時点でループを抜ける
}
}
Console.WriteLine(foundAt >= 0 ? $"インデックス {foundAt} に見つかりました" : "見つかりませんでした");
continue:現在のイテレーションをスキップする
// 偶数だけを処理し、奇数はスキップ
for (int i = 0; i < 10; i++)
{
if (i % 2 != 0)
continue; // 奇数は残りの処理をスキップして次のイテレーションへ
Console.Write($"{i} "); // 0 2 4 6 8
}
continue を使うと「処理したくない条件を先頭で弾く」ことができ、ネストを減らして読みやすくなります。条件に合致するものだけを深いブロックで処理するより、合致しないものを continue で早期スキップする書き方が推奨されます。
多重ループ(ネストループ)
2次元配列の処理
int[,] matrix = {
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};
for (int row = 0; row < 3; row++)
{
for (int col = 0; col < 3; col++)
{
Console.Write($"{matrix[row, col],3}"); // 幅3で右揃え
}
Console.WriteLine();
}
// 1 2 3
// 4 5 6
// 7 8 9
多重ループから一気に抜ける
C# にはラベル付き break がないため、多重ループを一気に抜ける場合は フラグ変数 または メソッドへの切り出し が定石です。
// 方法①: フラグ変数を使う
bool found = false;
for (int i = 0; i < 5 && !found; i++)
{
for (int j = 0; j < 5; j++)
{
if (i * j == 6)
{
Console.WriteLine($"見つかった: i={i}, j={j}");
found = true;
break;
}
}
}
// 方法②: メソッドに切り出して return(推奨:コードが一番クリーン)
static (int, int) FindProduct(int target)
{
for (int i = 0; i < 5; i++)
for (int j = 0; j < 5; j++)
if (i * j == target)
return (i, j); // return で多重ループを一度に抜けられる
return (-1, -1);
}
C# には goto 文があり多重ループからの脱出に使えますが、コードの流れが追いにくくなるため実務では禁忌とされます。メソッド分割やフラグ変数で解決してください。
for・foreach・while・do-while の使い分け
| 構文 | よく合うケース | 具体例 |
|---|---|---|
for |
繰り返し回数が明確・インデックスが必要・逆順や2刻みで進む | 配列の要素を末尾から削除・九九の表を出力 |
foreach |
コレクション全要素を順に処理・インデックス不要 | List の全要素を表示・Dictionary を巡回 |
while |
繰り返し回数が不明・条件が複雑・ファイルや入力ストリームの読み込み | ファイルの全行読み込み・外部APIのポーリング |
do-while |
最低1回は必ず実行・再入力・メニュー画面 | 「正しい値が入力されるまで繰り返す」バリデーション |
1. コレクション全体を処理するなら → foreach(最もシンプル)
2. 回数が決まっているなら → for(インデックスも使えて汎用的)
3. 回数が決まっていないなら → while
4. 最低1回は実行が必要なら → do-while
LINQ との使い分け
コレクションのフィルタリング・変換・集計には foreach ループより LINQ が適している場面があります。ループを書く前に「これは LINQ で書けないか?」を意識すると、より宣言的で読みやすいコードになります。
var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// foreach で偶数の合計を求める(命令的スタイル)
int sumLoop = 0;
foreach (var n in numbers)
{
if (n % 2 == 0)
sumLoop += n;
}
// LINQ で同じことを書く(宣言的スタイル)
int sumLinq = numbers.Where(n => n % 2 == 0).Sum();
Console.WriteLine(sumLoop); // 30
Console.WriteLine(sumLinq); // 30
よくある落とし穴と注意点
オフバイワンエラー(Off-by-one)
配列のインデックスは 0 始まりなので、i <= arr.Length と書くと最終要素の次(存在しない要素)にアクセスして IndexOutOfRangeException が発生します。条件は必ず i < arr.Length と書き、<= を使う場合は i <= arr.Length - 1 になります。
foreach中のコレクション変更
foreach でループ中に要素を追加・削除すると InvalidOperationException: コレクションが変更されました という例外が発生します。削除対象を別リストに収集してからループ外で削除するか、for 文で逆順にループする方法を使います。
無限ループ(ループ変数を更新し忘れる)
while 文でカウンタ変数の更新を忘れると無限ループになります。for 文は更新式をヘッダーに書くので忘れにくいですが、while 文ではループボディ内の更新忘れに注意してください。また、while (true) の無限ループでは必ず break または return の脱出条件を設けましょう。
浮動小数点のカウンタ変数
for 文のカウンタ変数に double や float を使うと、浮動小数点誤差で想定と異なる回数ループしたり、終了条件に到達しない場合があります。カウンタには整数型(int/long)を使い、小数のステップが必要なら整数インデックスから計算します。
LINQ クエリの遅延評価に注意
foreach でLINQクエリを反復すると、毎回クエリが再評価されます。同じクエリ結果を複数回使う場合は .ToList() や .ToArray() で具体化(マテリアライズ)してからループすることで、不要な再評価を防げます。
よくある質問
for 文がわずかに高速なケースがあります。List<T> に対してはほぼ同等です。ただし実務レベルでの差は無視できる程度で、可読性を優先して選ぶのが基本です。パフォーマンスが問題になった場合はプロファイリングで確認してから最適化します。for 文に切り替える(インデックスが必要なら for が自然)、② LINQ の Select((item, index) => (item, index)) で分解構文を使う。どちらが読みやすいかはケースバイケースですが、単純な添字アクセスなら for 文の方がシンプルです。for (var i = 0; i < 10; i++) と書くと i は int と推論されます。ただし var を使うメリットが少なく、チームによっては int i と明示する規約の場合もあります。読みやすさを優先して判断してください。まとめ
| 構文 | 特徴・使いどころ | 注意点 |
|---|---|---|
for |
回数固定・インデックスが必要・逆順/ステップ変更 | 境界値は < を使う(≦ はオフバイワンの温床) |
foreach |
コレクション全要素を順に処理・シンプル | ループ中にコレクションを変更できない |
while |
回数不定・条件による繰り返し・ストリーム読み込み | ループ変数の更新忘れで無限ループ |
do-while |
最低1回実行・入力バリデーション・メニュー | 末尾セミコロン(while(...) ;)を忘れない |
break |
ループを即座に終了・検索処理に必須 | 多重ループはメソッド分割で対処 |
continue |
現在のイテレーションをスキップ・ガード節として使う | 多用するとループの流れが追いにくくなる |
コレクションのフィルタリング・変換・集計には foreach の代わりに LINQ を検討しましょう。LINQの基本(Where・Select)で宣言的なコレクション処理を習得すると、ループを書く場面がさらに減ります。

