C#の多重ループとは?for・foreachの書き方と抜け出し方を初心者向けに解説

はじめに

C#でプログラムを書いていると、「配列の中身を順番に処理したい」「表のようなデータを行と列で扱いたい」「リストの中にあるリストをまとめて処理したい」という場面がよくあります。

このようなときに使われるのが多重ループです。

多重ループとは、for文やforeach文などのループ処理の中に、さらに別のループ処理を書く方法です。特に、2次元配列、表形式のデータ、座標、九九の表、リストの中のリストなどを扱うときによく使います。

一方で、初心者の方にとっては「外側のループと内側のループの動き方がわかりにくい」「breakでどこまで抜けるのかわからない」「配列の範囲外エラーが出る」といったつまずきやすいポイントもあります。

この記事では、C#の多重ループについて、for文とforeach文の基本から、配列・リストの処理方法、ループから抜け出す方法、よくあるエラー、実践サンプルまで初心者向けにわかりやすく解説します。

1. C#の多重ループとは?

1-1. 多重ループの基本的な意味

C#の多重ループとは、ループ文の中にさらにループ文を書くことです。

たとえば、次のようにfor文の中に別のfor文を書くと、2重ループになります。

C#
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 2; j++)
{
Console.WriteLine($"i={i}, j={j}");
}
}

このコードでは、外側のループであるiが1回進むたびに、内側のループであるjが最初から最後まで実行されます。

実行結果は次のようになります。

C#
i=0, j=0
i=0, j=1
i=1, j=0
i=1, j=1
i=2, j=0
i=2, j=1

外側のループが3回、内側のループが2回実行されるため、合計で3 × 2 = 6回処理が行われます。

1-2. 1重ループとの違い

1重ループは、単純に1つのまとまりを順番に処理するときに使います。

C#
for (int i = 0; i < 5; i++)
{
Console.WriteLine(i);
}

このコードは、0から4までを順番に表示します。

一方、多重ループは、複数の方向や階層を持つデータを処理するときに使います。

C#
for (int row = 0; row < 3; row++)
{
for (int col = 0; col < 4; col++)
{
Console.WriteLine($"行:{row}, 列:{col}");
}
}

このように、行と列のような組み合わせを扱う場合は、1重ループではなく2重ループを使うと自然に書けます。

1重ループは「横に1列並んだデータ」を処理するイメージです。

多重ループは「表のように縦横に並んだデータ」や「グループの中に複数の要素があるデータ」を処理するイメージです。

1-3. 多重ループが使われる代表的な場面

C#の多重ループは、次のような場面でよく使われます。

C#
// 2次元配列の処理
int[,] numbers = {
{ 1, 2, 3 },
{ 4, 5, 6 }
};

for (int row = 0; row < numbers.GetLength(0); row++)
{
for (int col = 0; col < numbers.GetLength(1); col++)
{
Console.WriteLine(numbers[row, col]);
}
}

代表的な用途としては、次のようなものがあります。

・2次元配列の全要素を処理する
・表の行と列を処理する
・九九の表を作る
・座標のX方向とY方向を処理する
・リストの中にあるリストを処理する
・複数の条件の組み合わせを調べる
・ゲームのマップや盤面を処理する

たとえば、3行3列のマス目を処理する場合、外側のループで行を進め、内側のループで列を進めるとわかりやすくなります。

C#
for (int y = 0; y < 3; y++)
{
for (int x = 0; x < 3; x++)
{
Console.WriteLine($"座標: ({x}, {y})");
}
}

このように、多重ループは「すべての組み合わせを順番に処理したい」ときに便利です。

1-4. 初心者が多重ループでつまずきやすいポイント

多重ループで初心者がつまずきやすいポイントは、主に次のようなものです。

まず、外側のループと内側のループの実行順序がわかりにくいことです。

C#
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 3; j++)
{
Console.WriteLine($"i={i}, j={j}");
}
}

この場合、i0の間に、j0, 1, 2まで動きます。その後、i1になり、再びj0, 1, 2まで動きます。

次に、ループ変数を間違えることもよくあります。

C#
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; i++)
{
Console.WriteLine(j);
}
}

このコードでは、内側のループで本来j++と書くべきところをi++と書いています。このようなミスをすると、意図しない動作や無限ループの原因になります。

また、breakを書いたときに、どのループを抜けるのかを勘違いすることもあります。C#のbreakは、基本的に一番近いループだけを抜けます。多重ループ全体を一気に抜けるわけではありません。

2. C#でfor文を使った多重ループの書き方

2-1. for文の基本構文

C#のfor文は、回数が決まっている繰り返し処理でよく使われます。

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

C#
for (初期化; 条件式; 更新)
{
繰り返したい処理;
}

具体的には、次のように書きます。

C#
for (int i = 0; i < 5; i++)
{
Console.WriteLine(i);
}

このコードでは、iの値が0から始まり、i < 5の間だけ処理を繰り返します。1回処理が終わるたびにi++によってiが1増えます。

実行結果は次のとおりです。

C#
0
1
2
3
4

for文は、配列やリストのインデックスを使って処理したいときに特に便利です。

C#
string[] names = { "田中", "佐藤", "鈴木" };

for (int i = 0; i < names.Length; i++)
{
Console.WriteLine(names[i]);
}

このように、iを配列の添字として使うことで、要素を順番に取り出せます。

2-2. for文を入れ子にする書き方

for文を入れ子にすると、多重ループになります。

C#
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 2; j++)
{
Console.WriteLine($"i={i}, j={j}");
}
}

外側のfor文の中に、内側のfor文を書いています。

多重ループでは、ループ変数の名前をわけることが重要です。外側のループではi、内側のループではjを使うことが多いです。

C#
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
Console.WriteLine($"{i}, {j}");
}
}

ただし、実務ではrowcolのように意味のある名前を使うと、さらに読みやすくなります。

C#
for (int row = 0; row < 3; row++)
{
for (int col = 0; col < 3; col++)
{
Console.WriteLine($"row={row}, col={col}");
}
}

行と列を扱う場合は、rowcolのような名前を使うと、外側と内側の役割がわかりやすくなります。

2-3. 2重forループのサンプルコード

次は、2重forループを使って、行と列の組み合わせを表示するサンプルです。

C#
for (int row = 1; row <= 3; row++)
{
for (int col = 1; col <= 4; col++)
{
Console.WriteLine($"行:{row}, 列:{col}");
}
}

実行結果は次のようになります。

C#
:1, :1
:1, :2
:1, :3
:1, :4
:2, :1
:2, :2
:2, :3
:2, :4
:3, :1
:3, :2
:3, :3
:3, :4

外側のrowが1回進むごとに、内側のcolが1から4まで繰り返されます。

つまり、処理の流れは次のようになります。

C#
row = 1 のとき col = 1, 2, 3, 4
row = 2 のとき col = 1, 2, 3, 4
row = 3 のとき col = 1, 2, 3, 4

外側のループ回数が3回、内側のループ回数が4回なので、合計処理回数は3 × 4 = 12回です。

2-4. 外側のループと内側のループの動き方

多重ループを理解するうえで大切なのは、内側のループは外側のループが1回進むたびに最初から実行されるという点です。

次のコードを見てみましょう。

C#
for (int i = 0; i < 2; i++)
{
Console.WriteLine($"外側開始 i={i}");

for (int j = 0; j < 3; j++)
{
Console.WriteLine($" 内側 j={j}");
}

Console.WriteLine($"外側終了 i={i}");
}

実行結果は次のようになります。

C#
外側開始 i=0
内側 j=0
内側 j=1
内側 j=2
外側終了 i=0
外側開始 i=1
内側 j=0
内側 j=1
内側 j=2
外側終了 i=1

このように、i=0のときに内側のjがすべて実行されます。その後、i=1になり、再びj=0から内側のループが始まります。

内側のループは、外側のループの1回分の処理の一部として実行されると考えると理解しやすくなります。

2-5. for文の多重ループで表や座標を扱う例

多重ループは、表のようなデータを扱うときに便利です。

次のコードでは、3行4列の表を表示しています。

C#
for (int row = 1; row <= 3; row++)
{
for (int col = 1; col <= 4; col++)
{
Console.Write($"[{row},{col}] ");
}

Console.WriteLine();
}

実行結果は次のようになります。

C#
[1,1] [1,2] [1,3] [1,4]
[2,1] [2,2] [2,3] [2,4]
[3,1] [3,2] [3,3] [3,4]

内側のループではConsole.Writeを使い、同じ行に表示しています。内側のループが終わった後にConsole.WriteLine()を実行することで、次の行に移動しています。

座標を扱う場合も考え方は同じです。

C#
for (int y = 0; y < 3; y++)
{
for (int x = 0; x < 3; x++)
{
Console.WriteLine($"X={x}, Y={y}");
}
}

ゲームのマップや盤面のように、X座標とY座標を順番に処理したい場合にも多重ループはよく使われます。

3. C#でforeach文を使った多重ループの書き方

3-1. foreach文の基本構文

C#のforeach文は、配列やリストなどのコレクションから要素を1つずつ取り出して処理するときに使います。

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

C#
foreach ( 変数名 in コレクション)
{
繰り返したい処理;
}

たとえば、文字列の配列を順番に表示する場合は次のように書きます。

C#
string[] fruits = { "りんご", "みかん", "バナナ" };

foreach (string fruit in fruits)
{
Console.WriteLine(fruit);
}

実行結果は次のとおりです。

C#
りんご
みかん
バナナ

foreach文は、インデックス番号を使わずに要素だけを順番に処理したい場合に向いています。

for文と比べて、配列の範囲外アクセスが起こりにくく、コードも読みやすくなりやすいです。

3-2. foreach文を入れ子にする書き方

foreach文も、for文と同じように入れ子にできます。

C#
string[][] groups =
{
new string[] { "田中", "佐藤" },
new string[] { "鈴木", "高橋" }
};

foreach (string[] group in groups)
{
foreach (string name in group)
{
Console.WriteLine(name);
}
}

このコードでは、外側のforeachでグループを1つずつ取り出し、内側のforeachで各グループ内の名前を1つずつ取り出しています。

実行結果は次のようになります。

C#
田中
佐藤
鈴木
高橋

foreachの多重ループは、配列の中に配列が入っている場合や、リストの中にリストが入っている場合に便利です。

3-3. 2次元配列をforeachで処理する例

C#の2次元配列は、foreachで全要素を順番に処理できます。

C#
int[,] numbers =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};

foreach (int number in numbers)
{
Console.WriteLine(number);
}

実行結果は次のようになります。

C#
1
2
3
4
5
6

このように、2次元配列に対してforeachを使うと、すべての要素を先頭から順番に取り出せます。

ただし、この書き方では行番号や列番号を直接使えません。

行と列を意識して処理したい場合は、for文を使うほうが向いています。

C#
int[,] numbers =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};

for (int row = 0; row < numbers.GetLength(0); row++)
{
for (int col = 0; col < numbers.GetLength(1); col++)
{
Console.WriteLine($"[{row},{col}] = {numbers[row, col]}");
}
}

要素だけを順番に取り出したいならforeach、行や列の位置も使いたいならfor、と考えるとわかりやすいです。

3-4. Listや配列の中にあるコレクションを処理する例

リストの中にリストが入っている場合も、foreachの多重ループで処理できます。

C#
List<List<string>> teams = new List<List<string>>
{
new List<string> { "田中", "佐藤" },
new List<string> { "鈴木", "高橋", "伊藤" },
new List<string> { "山田" }
};

foreach (List<string> team in teams)
{
foreach (string member in team)
{
Console.WriteLine(member);
}
}

実行結果は次のようになります。

C#
田中
佐藤
鈴木
高橋
伊藤
山田

このように、外側のforeachでチームを取り出し、内側のforeachでメンバーを取り出します。

チーム名も表示したい場合は、クラスや辞書を使う方法もありますが、基本的な考え方は同じです。

C#
List<List<int>> scores = new List<List<int>>
{
new List<int> { 80, 90 },
new List<int> { 70, 85, 95 }
};

foreach (List<int> scoreList in scores)
{
foreach (int score in scoreList)
{
Console.WriteLine(score);
}
}

コレクションの中にコレクションがある場合は、多重ループを使って外側から順番にたどっていきます。

3-5. for文とforeach文の使い分け

for文とforeach文は、どちらも繰り返し処理に使えますが、向いている場面が違います。

for文は、インデックスを使いたい場合に向いています。

C#
string[] names = { "田中", "佐藤", "鈴木" };

for (int i = 0; i < names.Length; i++)
{
Console.WriteLine($"{i}: {names[i]}");
}

実行結果は次のようになります。

C#
0: 田中
1: 佐藤
2: 鈴木

一方、foreach文は、要素だけを順番に処理したい場合に向いています。

C#
foreach (string name in names)
{
Console.WriteLine(name);
}

使い分けの目安は次のとおりです。

for文が向いているケースは、インデックス番号が必要な場合、指定した範囲だけ処理したい場合、逆順に処理したい場合、2次元配列の行と列を扱いたい場合です。

foreach文が向いているケースは、すべての要素を順番に処理したい場合、インデックスが不要な場合、コードをシンプルに書きたい場合です。

多重ループでも考え方は同じです。行番号や列番号が必要ならfor、要素だけを順番に取り出せればよいならforeachを使うとよいでしょう。

4. C#の多重ループで配列・リストを処理する方法

4-1. 2次元配列をfor文で処理する

C#の2次元配列は、行と列を持つ配列です。

C#
int[,] matrix =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};

この配列は、2行3列のデータです。

2次元配列をfor文で処理する場合は、GetLengthメソッドを使います。

C#
for (int row = 0; row < matrix.GetLength(0); row++)
{
for (int col = 0; col < matrix.GetLength(1); col++)
{
Console.WriteLine(matrix[row, col]);
}
}

matrix.GetLength(0)は行数を表します。

matrix.GetLength(1)は列数を表します。

そのため、外側のループで行を進め、内側のループで列を進めると、2次元配列の全要素を順番に処理できます。

要素の位置も表示する場合は、次のように書けます。

C#
for (int row = 0; row < matrix.GetLength(0); row++)
{
for (int col = 0; col < matrix.GetLength(1); col++)
{
Console.WriteLine($"matrix[{row}, {col}] = {matrix[row, col]}");
}
}

2次元配列を扱うときは、LengthではなくGetLength(0)GetLength(1)を使うのが基本です。

4-2. ジャグ配列を多重ループで処理する

C#には、2次元配列のほかにジャグ配列があります。

ジャグ配列とは、配列の中に配列が入っている形の配列です。

C#
int[][] jaggedArray =
{
new int[] { 1, 2 },
new int[] { 3, 4, 5 },
new int[] { 6 }
};

ジャグ配列では、各行の要素数が異なっていても問題ありません。

ジャグ配列を多重ループで処理する場合は、次のように書きます。

C#
for (int i = 0; i < jaggedArray.Length; i++)
{
for (int j = 0; j < jaggedArray[i].Length; j++)
{
Console.WriteLine(jaggedArray[i][j]);
}
}

2次元配列ではmatrix[row, col]のように書きますが、ジャグ配列ではjaggedArray[i][j]のように書きます。

それぞれの行で要素数が違うため、内側のループ条件にはjaggedArray[i].Lengthを使います。

C#
for (int row = 0; row < jaggedArray.Length; row++)
{
for (int col = 0; col < jaggedArray[row].Length; col++)
{
Console.WriteLine($"[{row}][{col}] = {jaggedArray[row][col]}");
}
}

ジャグ配列では、外側の配列から内側の配列を取り出し、その内側の配列の長さを使って処理することが大切です。

4-3. List<List<T>>を多重ループで処理する

List<List<T>>は、リストの中にリストが入っている形です。

たとえば、整数のリストを複数持つ場合は次のように書けます。

C#
List<List<int>> numberGroups = new List<List<int>>
{
new List<int> { 1, 2, 3 },
new List<int> { 4, 5 },
new List<int> { 6, 7, 8, 9 }
};

for文で処理する場合は、次のように書きます。

C#
for (int i = 0; i < numberGroups.Count; i++)
{
for (int j = 0; j < numberGroups[i].Count; j++)
{
Console.WriteLine(numberGroups[i][j]);
}
}

Listでは、配列のLengthではなくCountを使います。

位置も表示したい場合は、次のように書けます。

C#
for (int groupIndex = 0; groupIndex < numberGroups.Count; groupIndex++)
{
for (int itemIndex = 0; itemIndex < numberGroups[groupIndex].Count; itemIndex++)
{
Console.WriteLine($"グループ:{groupIndex}, 要素:{itemIndex}, 値:{numberGroups[groupIndex][itemIndex]}");
}
}

foreach文で書くこともできます。

C#
foreach (List<int> group in numberGroups)
{
foreach (int number in group)
{
Console.WriteLine(number);
}
}

インデックスが必要な場合はfor、要素だけを処理したい場合はforeachを使うとよいでしょう。

4-4. インデックスが必要な場合の書き方

配列やリストの中身を処理するとき、要素の値だけでなく「何番目の要素なのか」が必要になることがあります。

その場合は、for文を使うのが基本です。

C#
string[] names = { "田中", "佐藤", "鈴木" };

for (int i = 0; i < names.Length; i++)
{
Console.WriteLine($"{i}番目: {names[i]}");
}

多重ループでも同じです。

C#
int[,] scores =
{
{ 80, 90, 70 },
{ 60, 75, 85 }
};

for (int row = 0; row < scores.GetLength(0); row++)
{
for (int col = 0; col < scores.GetLength(1); col++)
{
Console.WriteLine($"[{row},{col}] = {scores[row, col]}");
}
}

このように、行番号や列番号が必要な場合は、for文を使うと自然に書けます。

リストの中のリストでも、インデックスが必要なら次のように書きます。

C#
List<List<string>> groups = new List<List<string>>
{
new List<string> { "A", "B" },
new List<string> { "C", "D", "E" }
};

for (int groupIndex = 0; groupIndex < groups.Count; groupIndex++)
{
for (int itemIndex = 0; itemIndex < groups[groupIndex].Count; itemIndex++)
{
Console.WriteLine($"groups[{groupIndex}][{itemIndex}] = {groups[groupIndex][itemIndex]}");
}
}

ijでも問題ありませんが、初心者のうちはrowcolgroupIndexitemIndexのように意味がわかる名前を使うとミスを減らせます。

4-5. 要素だけを順番に取り出したい場合の書き方

インデックスが不要で、要素だけを順番に処理したい場合は、foreach文を使うとシンプルです。

C#
int[] numbers = { 10, 20, 30 };

foreach (int number in numbers)
{
Console.WriteLine(number);
}

リストの中にリストがある場合も、foreachの多重ループで簡単に書けます。

C#
List<List<string>> groups = new List<List<string>>
{
new List<string> { "田中", "佐藤" },
new List<string> { "鈴木", "高橋" }
};

foreach (List<string> group in groups)
{
foreach (string name in group)
{
Console.WriteLine(name);
}
}

2次元配列も、要素だけを順番に取り出すならforeachで書けます。

C#
int[,] matrix =
{
{ 1, 2 },
{ 3, 4 }
};

foreach (int value in matrix)
{
Console.WriteLine(value);
}

ただし、この方法では行番号や列番号を取得しづらいため、位置情報が必要な場合はfor文の多重ループを使うほうがわかりやすいです。

5. C#の多重ループから抜け出す方法

5-1. breakで内側のループを抜ける

C#では、breakを使うとループを途中で終了できます。

ただし、多重ループの中でbreakを使った場合、抜けるのは一番近いループだけです。

C#
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
if (j == 2)
{
break;
}

Console.WriteLine($"i={i}, j={j}");
}
}

実行結果は次のようになります。

C#
i=0, j=0
i=0, j=1
i=1, j=0
i=1, j=1
i=2, j=0
i=2, j=1

内側のループでj == 2になるとbreakが実行され、内側のループだけを抜けます。

しかし、外側のループは続くため、i=1i=2の場合も内側のループが実行されます。

5-2. continueで次の繰り返しに進む

continueを使うと、現在の繰り返し処理をスキップして、次の繰り返しに進みます。

C#
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 5; j++)
{
if (j == 2)
{
continue;
}

Console.WriteLine($"i={i}, j={j}");
}
}

実行結果は次のようになります。

C#
i=0, j=0
i=0, j=1
i=0, j=3
i=0, j=4
i=1, j=0
i=1, j=1
i=1, j=3
i=1, j=4

j == 2のときだけConsole.WriteLineが実行されず、次のjに進みます。

continuebreakと同じく、一番近いループに対して作用します。

内側のループでcontinueを書いた場合は、内側のループの次の繰り返しに進みます。外側のループを直接次に進めるわけではありません。

5-3. breakでは外側のループまで抜けられない理由

多重ループでよくある疑問が、「breakを書いたのに外側のループが止まらない」というものです。

次のコードを見てください。

C#
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (i == 1 && j == 1)
{
break;
}

Console.WriteLine($"i={i}, j={j}");
}
}

このコードでは、i == 1 && j == 1になったときにbreakが実行されます。

しかし、このbreakで抜けるのは内側のfor文だけです。外側のfor文はそのまま続きます。

つまり、処理は次のようなイメージです。

C#
外側 i=0
内側 j=0
内側 j=1
内側 j=2

外側 i=1
内側 j=0
内側 j=1 で内側だけ終了

外側 i=2
内側 j=0
内側 j=1
内側 j=2

breakは、基本的に自分が書かれている位置から見て一番近いループを終了します。

そのため、多重ループ全体を抜けたい場合は、別の工夫が必要です。

5-4. フラグ変数を使って多重ループを抜ける方法

多重ループ全体を抜けたい場合、フラグ変数を使う方法があります。

C#
bool found = false;

for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (i == 1 && j == 1)
{
found = true;
break;
}

Console.WriteLine($"i={i}, j={j}");
}

if (found)
{
break;
}
}

このコードでは、条件に一致したらfoundtrueにして、まず内側のループをbreakで抜けます。

その後、外側のループ側でもfoundを確認し、trueなら外側のループもbreakします。

配列から目的の値を探す例も見てみましょう。

C#
int[,] numbers =
{
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};

int target = 5;
bool isFound = false;

for (int row = 0; row < numbers.GetLength(0); row++)
{
for (int col = 0; col < numbers.GetLength(1); col++)
{
if (numbers[row, col] == target)
{
Console.WriteLine($"見つかりました: row={row}, col={col}");
isFound = true;
break;
}
}

if (isFound)
{
break;
}
}

フラグ変数を使う方法は、初心者にもわかりやすく、多重ループを抜ける代表的な書き方です。

5-5. メソッド化してreturnで抜ける方法

多重ループ全体を抜けたい場合は、処理をメソッドに分けてreturnで抜ける方法もあります。

C#
static void FindNumber()
{
int[,] numbers =
{
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};

int target = 5;

for (int row = 0; row < numbers.GetLength(0); row++)
{
for (int col = 0; col < numbers.GetLength(1); col++)
{
if (numbers[row, col] == target)
{
Console.WriteLine($"見つかりました: row={row}, col={col}");
return;
}
}
}

Console.WriteLine("見つかりませんでした");
}

returnが実行されると、そのメソッド自体が終了します。

そのため、内側のループだけでなく、外側のループも含めて処理を終了できます。

値を返すメソッドにすることもできます。

C#
static bool ContainsNumber(int[,] numbers, int target)
{
for (int row = 0; row < numbers.GetLength(0); row++)
{
for (int col = 0; col < numbers.GetLength(1); col++)
{
if (numbers[row, col] == target)
{
return true;
}
}
}

return false;
}

使い方は次のとおりです。

C#
int[,] numbers =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};

bool result = ContainsNumber(numbers, 5);
Console.WriteLine(result);

多重ループの中で目的の値を見つけたらすぐに処理を終えたい場合、メソッド化してreturnを使う方法はとても実用的です。

5-6. gotoを使う方法と注意点

C#では、gotoを使って多重ループを抜けることもできます。

C#
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (i == 1 && j == 1)
{
goto EndLoop;
}

Console.WriteLine($"i={i}, j={j}");
}
}

EndLoop:
Console.WriteLine("ループを抜けました");

goto EndLoop;が実行されると、EndLoop:というラベルの位置まで移動します。

そのため、内側のループだけでなく外側のループも一気に抜けられます。

ただし、gotoは使いすぎるとコードの流れが追いにくくなります。どこからどこへ処理が移動するのかわかりにくくなり、保守しづらいコードになりやすいです。

初心者のうちは、まず次の方法を優先するのがおすすめです。

・フラグ変数を使う
・処理をメソッドに分けてreturnする
・そもそも多重ループを浅くできないか考える

gotoは、どうしてもコードが複雑になる場合の選択肢として覚えておく程度で十分です。

6. C#の多重ループでよくあるエラーと注意点

6-1. ループ変数を間違える

多重ループでは、外側と内側で複数のループ変数を使います。そのため、変数名を間違えるミスがよく起こります。

たとえば、次のコードは間違いです。

C#
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; i++)
{
Console.WriteLine($"i={i}, j={j}");
}
}

内側のループでj++と書くべきところを、間違えてi++と書いています。

正しくは次のように書きます。

C#
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
Console.WriteLine($"i={i}, j={j}");
}
}

このようなミスを防ぐには、変数名を意味のある名前にするのも有効です。

C#
for (int row = 0; row < 3; row++)
{
for (int col = 0; col < 3; col++)
{
Console.WriteLine($"row={row}, col={col}");
}
}

rowcolのように役割がわかる名前にすると、外側と内側を間違えにくくなります。

6-2. 終了条件を間違えて無限ループになる

多重ループでは、終了条件を間違えると無限ループになることがあります。

C#
for (int i = 0; i < 3; i++)
{
for (int j = 0; i < 3; j++)
{
Console.WriteLine(j);
}
}

このコードでは、内側のループ条件にi < 3を使ってしまっています。

内側のループではjが増え続けますが、条件に使われているiは内側のループ中に変わらないため、意図しない無限ループになる可能性があります。

正しくは次のように、内側のループ条件にはjを使います。

C#
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
Console.WriteLine(j);
}
}

多重ループを書くときは、初期化、条件式、更新式で同じループ変数を使っているか確認しましょう。

C#
for (int row = 0; row < 3; row++)
{
for (int col = 0; col < 3; col++)
{
Console.WriteLine($"{row}, {col}");
}
}

rowのループではrowを更新し、colのループではcolを更新する、という対応関係を意識することが大切です。

6-3. 配列の範囲外アクセスが発生する

C#で配列の範囲外にアクセスすると、IndexOutOfRangeExceptionが発生します。

たとえば、次のコードはエラーになります。

C#
int[] numbers = { 10, 20, 30 };

for (int i = 0; i <= numbers.Length; i++)
{
Console.WriteLine(numbers[i]);
}

配列のインデックスは0から始まるため、要素数が3の場合、使えるインデックスは0, 1, 2です。

しかし、i <= numbers.Lengthと書くと、最後にi == 3の状態でnumbers[3]へアクセスしてしまいます。

正しくは、i < numbers.Lengthです。

C#
for (int i = 0; i < numbers.Length; i++)
{
Console.WriteLine(numbers[i]);
}

2次元配列でも同じです。

C#
int[,] matrix =
{
{ 1, 2, 3 },
{ 4, 5, 6 }
};

for (int row = 0; row < matrix.GetLength(0); row++)
{
for (int col = 0; col < matrix.GetLength(1); col++)
{
Console.WriteLine(matrix[row, col]);
}
}

配列を処理するときは、終了条件に<を使うことが基本です。

6-4. 処理回数が増えすぎて遅くなる

多重ループでは、外側のループ回数と内側のループ回数を掛け合わせた回数だけ処理が行われます。

C#
for (int i = 0; i < 1000; i++)
{
for (int j = 0; j < 1000; j++)
{
// 何らかの処理
}
}

このコードでは、処理回数は1000 × 1000 = 1,000,000回になります。

3重ループになると、さらに処理回数は増えます。

C#
for (int i = 0; i < 100; i++)
{
for (int j = 0; j < 100; j++)
{
for (int k = 0; k < 100; k++)
{
// 何らかの処理
}
}
}

この場合、100 × 100 × 100 = 1,000,000回の処理になります。

多重ループを書くときは、処理回数がどれくらいになるかを意識することが大切です。

特に、大量のデータを扱う場合や、内側の処理が重い場合は注意が必要です。

無駄なループを減らしたり、条件に一致したら早めにbreakしたり、処理方法を見直したりすることで、パフォーマンスを改善できる場合があります。

6-5. ネストが深くなりコードが読みにくくなる

多重ループは便利ですが、ネストが深くなるとコードが読みにくくなります。

C#
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
for (int k = 0; k < 10; k++)
{
for (int l = 0; l < 10; l++)
{
Console.WriteLine($"{i}, {j}, {k}, {l}");
}
}
}
}

このように4重、5重と深くなると、どのループが何をしているのかわかりにくくなります。

また、breakcontinueがどのループに対して働いているのかも追いづらくなります。

ネストが深くなりすぎる場合は、次のような改善を考えましょう。

・処理をメソッドに分ける
・ループの中の処理を短くする
・必要のないループを減らす
・データ構造を見直す
・LINQで簡潔に書けないか考える

多重ループは便利ですが、読みやすさを保つことも重要です。

7. C#の多重ループをわかりやすく書くコツ

7-1. ループ変数名をわかりやすくする

多重ループでは、ループ変数の名前がとても重要です。

短いサンプルコードでは、ijを使うことが多いです。

C#
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
Console.WriteLine($"{i}, {j}");
}
}

ただし、実際のコードでは、何を表しているのかわかる名前にしたほうが読みやすくなります。

C#
for (int row = 0; row < 3; row++)
{
for (int col = 0; col < 3; col++)
{
Console.WriteLine($"row={row}, col={col}");
}
}

リストの中のリストを処理する場合も、意味のある名前を使いましょう。

C#
for (int groupIndex = 0; groupIndex < groups.Count; groupIndex++)
{
for (int memberIndex = 0; memberIndex < groups[groupIndex].Count; memberIndex++)
{
Console.WriteLine(groups[groupIndex][memberIndex]);
}
}

変数名がわかりやすいと、外側のループと内側のループの役割を理解しやすくなります。

7-2. ネストを深くしすぎない

多重ループは、2重程度であれば比較的読みやすいですが、3重、4重になると一気に複雑になります。

C#
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
for (int k = 0; k < 10; k++)
{
Console.WriteLine($"{i}, {j}, {k}");
}
}
}

このようなコードが必ず悪いわけではありませんが、処理の意味がわかりにくい場合は改善を考えたほうがよいです。

たとえば、内側の処理をメソッドに分けると読みやすくなります。

C#
for (int row = 0; row < matrix.GetLength(0); row++)
{
ProcessRow(matrix, row);
}
C#
static void ProcessRow(int[,] matrix, int row)
{
for (int col = 0; col < matrix.GetLength(1); col++)
{
Console.WriteLine(matrix[row, col]);
}
}

ネストが深くなりそうなときは、「この処理は別のメソッドに分けられないか」と考えるとよいでしょう。

7-3. 処理をメソッドに分ける

多重ループの中に長い処理を書くと、コード全体が読みにくくなります。

C#
for (int row = 0; row < scores.GetLength(0); row++)
{
for (int col = 0; col < scores.GetLength(1); col++)
{
int score = scores[row, col];

if (score >= 80)
{
Console.WriteLine($"高得点: {score}");
}
else
{
Console.WriteLine($"通常: {score}");
}
}
}

この程度なら問題ありませんが、条件分岐や計算が増えてくると見通しが悪くなります。

その場合は、内側の処理をメソッドに分けます。

C#
for (int row = 0; row < scores.GetLength(0); row++)
{
for (int col = 0; col < scores.GetLength(1); col++)
{
PrintScore(scores[row, col]);
}
}
C#
static void PrintScore(int score)
{
if (score >= 80)
{
Console.WriteLine($"高得点: {score}");
}
else
{
Console.WriteLine($"通常: {score}");
}
}

メソッドに分けることで、多重ループの部分は「全体を順番に処理している」とわかりやすくなります。

7-4. LINQで置き換えられるケースを考える

C#では、LINQを使ってコレクション処理を簡潔に書ける場合があります。

たとえば、リストの中のリストからすべての数値を取り出したい場合、多重ループでは次のように書けます。

C#
List<List<int>> groups = new List<List<int>>
{
new List<int> { 1, 2 },
new List<int> { 3, 4, 5 }
};

List<int> result = new List<int>();

foreach (List<int> group in groups)
{
foreach (int number in group)
{
result.Add(number);
}
}

LINQのSelectManyを使うと、次のように書けます。

C#
List<int> result = groups.SelectMany(group => group).ToList();

また、条件に一致する要素だけを取り出したい場合もLINQが便利です。

C#
List<int> evenNumbers = groups
.SelectMany(group => group)
.Where(number => number % 2 == 0)
.ToList();

ただし、初心者のうちは、まず多重ループで処理の流れを理解することが大切です。

LINQは便利ですが、複雑に書きすぎると逆に読みにくくなることもあります。

処理の意図がわかりやすいほうを選びましょう。

7-5. コメントで外側と内側の処理を整理する

多重ループは、外側と内側の役割をコメントで整理すると読みやすくなります。

C#
// 行を順番に処理する
for (int row = 0; row < matrix.GetLength(0); row++)
{
// 現在の行の列を順番に処理する
for (int col = 0; col < matrix.GetLength(1); col++)
{
Console.WriteLine(matrix[row, col]);
}
}

コメントを書くことで、後から見たときに「外側は行、内側は列を処理している」とすぐにわかります。

ただし、コメントを書きすぎる必要はありません。

変数名で意味が伝わるなら、コメントは少なくても問題ありません。

C#
for (int row = 0; row < matrix.GetLength(0); row++)
{
for (int col = 0; col < matrix.GetLength(1); col++)
{
Console.WriteLine(matrix[row, col]);
}
}

このように、rowcolという名前だけでも、行と列を処理していることが伝わります。

コメントは、コードだけでは意図が伝わりにくい部分に書くと効果的です。

8. C#の多重ループに関する実践サンプル

8-1. 九九の表を表示する

多重ループの定番サンプルが、九九の表です。

C#
for (int i = 1; i <= 9; i++)
{
for (int j = 1; j <= 9; j++)
{
Console.Write($"{i * j}\t");
}

Console.WriteLine();
}

実行結果は次のようになります。

C#
1	2	3	4	5	6	7	8	9
2 4 6 8 10 12 14 16 18
3 6 9 12 15 18 21 24 27
4 8 12 16 20 24 28 32 36
5 10 15 20 25 30 35 40 45
6 12 18 24 30 36 42 48 54
7 14 21 28 35 42 49 56 63
8 16 24 32 40 48 56 64 72
9 18 27 36 45 54 63 72 81

外側のループで段を表し、内側のループで掛ける数を表しています。

C#
i = 1 のとき、1×1 から 1×9
i = 2 のとき、2×1 から 2×9
i = 3 のとき、3×1 から 3×9

内側のループが終わるたびにConsole.WriteLine()で改行することで、表のように表示できます。

8-2. 2次元配列の全要素を表示する

次は、2次元配列の全要素を表示するサンプルです。

C#
int[,] numbers =
{
{ 10, 20, 30 },
{ 40, 50, 60 },
{ 70, 80, 90 }
};

for (int row = 0; row < numbers.GetLength(0); row++)
{
for (int col = 0; col < numbers.GetLength(1); col++)
{
Console.Write($"{numbers[row, col]} ");
}

Console.WriteLine();
}

実行結果は次のようになります。

C#
10 20 30
40 50 60
70 80 90

numbers.GetLength(0)で行数を取得し、numbers.GetLength(1)で列数を取得しています。

この書き方を使えば、配列のサイズが変わっても同じコードで処理できます。

C#
int[,] numbers =
{
{ 1, 2 },
{ 3, 4 },
{ 5, 6 }
};

このように配列の行数や列数が変わっても、GetLengthを使っていれば範囲外アクセスを防ぎやすくなります。

8-3. 条件に一致する値を探してループを抜ける

多重ループでは、目的の値が見つかったら処理を終了したい場合があります。

次のコードでは、2次元配列から指定した値を探しています。

C#
int[,] numbers =
{
{ 3, 8, 12 },
{ 5, 9, 15 },
{ 7, 11, 20 }
};

int target = 9;
bool found = false;

for (int row = 0; row < numbers.GetLength(0); row++)
{
for (int col = 0; col < numbers.GetLength(1); col++)
{
if (numbers[row, col] == target)
{
Console.WriteLine($"見つかりました: row={row}, col={col}");
found = true;
break;
}
}

if (found)
{
break;
}
}

if (!found)
{
Console.WriteLine("見つかりませんでした");
}

実行結果は次のようになります。

C#
見つかりました: row=1, col=1

内側のループで値が見つかったら、まず内側のループをbreakします。

その後、外側のループでもfoundを確認してbreakします。

この方法を使うと、多重ループ全体を途中で終了できます。

8-4. リスト内のリストを順番に処理する

次は、List<List<string>>を多重ループで処理するサンプルです。

C#
List<List<string>> classes = new List<List<string>>
{
new List<string> { "田中", "佐藤" },
new List<string> { "鈴木", "高橋", "伊藤" },
new List<string> { "山田", "中村" }
};

for (int classIndex = 0; classIndex < classes.Count; classIndex++)
{
Console.WriteLine($"{classIndex + 1}組");

for (int studentIndex = 0; studentIndex < classes[classIndex].Count; studentIndex++)
{
Console.WriteLine($" {classes[classIndex][studentIndex]}");
}
}

実行結果は次のようになります。

C#
1
田中
佐藤
2
鈴木
高橋
伊藤
3
山田
中村

外側のループでクラスを処理し、内側のループで生徒を処理しています。

foreachで書くこともできます。

C#
foreach (List<string> classMembers in classes)
{
foreach (string student in classMembers)
{
Console.WriteLine(student);
}
}

クラス番号や生徒番号が必要な場合はfor文、名前だけを表示したい場合はforeach文が向いています。

8-5. 多重ループの処理結果を画面に出力する

多重ループでは、計算結果を表のように画面へ出力することもよくあります。

次のコードでは、行番号と列番号を足した値を表示しています。

C#
for (int row = 1; row <= 5; row++)
{
for (int col = 1; col <= 5; col++)
{
int result = row + col;
Console.Write($"{result}\t");
}

Console.WriteLine();
}

実行結果は次のようになります。

C#
2	3	4	5	6
3 4 5 6 7
4 5 6 7 8
5 6 7 8 9
6 7 8 9 10

Console.Writeを使うと同じ行に出力できます。

内側のループが終わった後にConsole.WriteLine()を使うと改行できます。

この組み合わせは、表形式の結果を表示するときによく使います。

C#
for (int row = 1; row <= 3; row++)
{
for (int col = 1; col <= 3; col++)
{
Console.Write($"[{row},{col}] ");
}

Console.WriteLine();
}

実行結果は次のようになります。

C#
[1,1] [1,2] [1,3]
[2,1] [2,2] [2,3]
[3,1] [3,2] [3,3]

多重ループと画面出力を組み合わせると、表、マップ、座標、計算結果などをわかりやすく表示できます。

9. C#の多重ループに関するよくある質問

9-1. C#で多重ループは何重まで書ける?

C#では、文法上は2重ループだけでなく、3重ループ、4重ループのようにさらに深く書くこともできます。

C#
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
for (int k = 0; k < 3; k++)
{
Console.WriteLine($"{i}, {j}, {k}");
}
}
}

ただし、何重にもネストすると、コードが読みにくくなります。

また、処理回数も急激に増えます。

たとえば、各ループが100回ずつ実行される3重ループでは、合計で100 × 100 × 100 = 1,000,000回の処理になります。

実務では、2重ループまでならよく使われますが、3重以上になる場合は、処理をメソッドに分けたり、データ構造を見直したりすることを考えたほうがよいです。

9-2. forとforeachはどちらを使うべき?

forforeachのどちらを使うべきかは、インデックスが必要かどうかで考えるとわかりやすいです。

インデックスが必要な場合はfor文を使います。

C#
for (int i = 0; i < names.Length; i++)
{
Console.WriteLine($"{i}: {names[i]}");
}

要素だけを順番に取り出したい場合はforeach文を使います。

C#
foreach (string name in names)
{
Console.WriteLine(name);
}

2次元配列で行番号や列番号を使いたい場合は、for文の多重ループが向いています。

C#
for (int row = 0; row < matrix.GetLength(0); row++)
{
for (int col = 0; col < matrix.GetLength(1); col++)
{
Console.WriteLine(matrix[row, col]);
}
}

一方、リスト内のリストから要素を順番に取り出すだけなら、foreach文の多重ループが読みやすいことが多いです。

C#
foreach (List<string> group in groups)
{
foreach (string name in group)
{
Console.WriteLine(name);
}
}

9-3. 多重ループを一気に抜けるにはどうすればいい?

C#のbreakは、一番近いループだけを抜けます。

そのため、多重ループ全体を一気に抜けたい場合は、フラグ変数を使う方法や、メソッド化してreturnする方法があります。

フラグ変数を使う例は次のとおりです。

C#
bool found = false;

for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (i == 1 && j == 1)
{
found = true;
break;
}
}

if (found)
{
break;
}
}

メソッド化してreturnする例は次のとおりです。

C#
static bool FindValue(int[,] numbers, int target)
{
for (int row = 0; row < numbers.GetLength(0); row++)
{
for (int col = 0; col < numbers.GetLength(1); col++)
{
if (numbers[row, col] == target)
{
return true;
}
}
}

return false;
}

多重ループから抜ける処理が複雑になる場合は、メソッドに分けてreturnする方法が読みやすくなることが多いです。

9-4. 多重ループは処理速度に影響する?

多重ループは、処理回数が増えやすいため、処理速度に影響することがあります。

たとえば、次の2重ループでは100万回処理が行われます。

C#
for (int i = 0; i < 1000; i++)
{
for (int j = 0; j < 1000; j++)
{
// 処理
}
}

外側が1000回、内側が1000回なので、合計で1,000,000回です。

もし内側の処理が重い場合、実行時間が長くなる可能性があります。

対策としては、次のような方法があります。

・不要なループを減らす
・条件に一致したら早めにbreakする
・同じ計算を何度も行わない
・データ構造を見直す
・LINQや辞書を使って検索処理を効率化する

多重ループそのものが悪いわけではありませんが、処理回数を意識して書くことが大切です。

9-5. 多重ループを使わない書き方はある?

処理内容によっては、多重ループを使わずに書ける場合もあります。

たとえば、List<List<int>>の中身をすべて1つのリストにまとめたい場合、通常の多重ループでは次のように書けます。

C#
List<int> result = new List<int>();

foreach (List<int> group in groups)
{
foreach (int number in group)
{
result.Add(number);
}
}

LINQのSelectManyを使うと、次のように書けます。

C#
List<int> result = groups.SelectMany(group => group).ToList();

条件に一致する値を取り出したい場合も、LINQで書けます。

C#
List<int> evenNumbers = groups
.SelectMany(group => group)
.Where(number => number % 2 == 0)
.ToList();

ただし、すべての多重ループをLINQに置き換えればよいわけではありません。

複雑な処理や、行と列を明確に扱いたい処理では、多重ループのほうがわかりやすい場合もあります。

初心者のうちは、まずfor文やforeach文の多重ループを理解し、そのうえでLINQも選択肢として覚えるとよいでしょう。

まとめ

C#の多重ループとは、for文やforeach文などのループ処理の中に、さらにループ処理を書く方法です。

多重ループを使うと、2次元配列、ジャグ配列、List<List<T>>、表形式のデータ、座標、九九の表などを効率よく処理できます。

for文の多重ループは、インデックスを使って行や列を扱いたい場合に向いています。

C#
for (int row = 0; row < matrix.GetLength(0); row++)
{
for (int col = 0; col < matrix.GetLength(1); col++)
{
Console.WriteLine(matrix[row, col]);
}
}

foreach文の多重ループは、要素だけを順番に取り出したい場合に向いています。

C#
foreach (List<string> group in groups)
{
foreach (string name in group)
{
Console.WriteLine(name);
}
}

また、多重ループの中でbreakを使うと、一番近いループだけを抜けます。外側のループまで抜けたい場合は、フラグ変数を使う方法や、メソッド化してreturnする方法を使います。

多重ループを書くときは、ループ変数の間違い、終了条件のミス、配列の範囲外アクセス、処理回数の増加、ネストの深さに注意しましょう。

初心者の方は、まず2重ループの動き方をしっかり理解することが大切です。外側のループが1回進むたびに、内側のループが最初から最後まで実行される、という基本を押さえれば、C#の多重ループはぐっと理解しやすくなります。