Menu

C#のfor文:構文、逆順ループ、ネストしたループ、落とし穴

C#のforループの動き方を解説します。初期化子、条件、反復子、逆順のカウントと増分の指定、複数のループ変数、インデックスによる配列のループ、ネストしたループ、for(;;)、そしてオフバイワンと反復中の削除によるバグを扱います。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

for ループは、条件が真である間ブロックを繰り返し、準備と増分をループのヘッダーに書きます。決まった回数の繰り返しや、配列のすべてのインデックスなど、数を数えるときに使うループです。

出力:

Lap 1 of 5
Lap 2 of 5
Lap 3 of 5
Lap 4 of 5
Lap 5 of 5

3つの部分が実行される順序

for (initializer; condition; iterator) は決まった順序で実行されます。

  1. 初期化子(int i = 1)が、何よりも先に1回だけ実行されます。
  2. 条件(i <= 5)が確認されます。falseならループは終わり、本体は二度と実行されません。
  3. 本体が実行されます。
  4. 反復子(i++)が実行され、手順2に戻ります。

条件は各反復の前に確認されるので、for ループは1回も実行されないことがあります。for (int i = 10; i < 5; i++) は本体に一度も入りません。上のループは次の while ループと同じように動きます。

int i = 1;
while (i <= 5)
{
    Console.WriteLine($"Lap {i} of 5");
    i++;
}

2つの形には1つだけ違いがあります。for の本体の中の continue は反復子を実行しますが、この while 版では i++ を飛び越えてしまうので、同じ i を再び判定して、いつまでも回り続けるおそれがあります。初期化子で宣言した変数はループの中にしか存在しません。後で最終値が必要なら、ループの前に宣言します:int i; for (i = 0; ...; i++) { }。

逆順のカウントと増分

反復子には任意の式を書けます。減らせば逆順に数え、1より大きい値を足せば飛び飛びに進み、掛け算なら等比数列になります。

出力:

3... 2... 1... liftoff
 00 15 30 45
 1 10 100 1000

反復子が条件をfalseにする方向へ進むようにします。for (int i = 0; i < 10; i--) は10から遠ざかる方向に数え、20億回ほど反復して i が一周したときにやっと止まります。

インデックスで配列をループする

配列には Length、リストには Count があり、有効なインデックスは 0 からそれより1小さい値までです。インデックスがあると、foreach にはないもの、つまり位置、隣の要素、そして書き戻しの権利が得られます。

出力:

#1: 17.99
#2: 4.50
#3: 38.25
#4: 7.43
item 1 vs item 0: down
item 2 vs item 1: up
item 3 vs item 2: down

最後のループは、各要素をその1つ前の要素と比較するので 1 から始まります。配列そのものの作成、並べ替え、コピーは配列のページで扱います。

複数のループ変数

初期化子では同じ型の変数を複数宣言でき、反復子ではカンマで区切って複数の変数を更新できます。典型的な使い方は、2つのインデックスが互いに近づいていく形です。

出力:

racecar is a palindrome: True
9, 1, 8, 3

1つの宣言の中では両方の変数が同じ型でなければなりません。int left = 0, right = 10 は動きますが、int i = 0, string s = "" は動きません。

ネストしたループ

ループの中のループは、外側のループの反復ごとに、その範囲全体を1回ずつ実行します。表やグリッドが典型的な例です。

出力:

   1   2   3   4
   2   4   6   8
   3   6   9  12
   4   8  12  16

内側の本体は16回(4 × 4)実行されます。ネストすると処理量はすぐに膨らみます。それぞれ10,000要素の2重ループは1億回の反復になります。内側のループでの break は内側のループから抜けるだけです。両方から抜ける方法はbreakとcontinueのページで紹介しています。

無限ループ:for(;;)

ヘッダーの3つの部分はすべて省略可能です。条件がないと、ループは中の何かが抜け出すまで実行されます。

出力:

Month 1: 70 left
Month 2: 40 left
Month 3: 10 left
Overdrawn in month 4: -20

for (;;) と while (true) は同じコードにコンパイルされます。コードベースで使われているほうを選びます。

オフバイワンエラー

for の最もよくあるバグは、条件が1つ先まで進んでしまうことです。

出力:

Ada
Grace
Linus
IndexOutOfRangeException at index 3

要素数3の配列のインデックスは0、1、2なので、正しい条件は i < names.Length です。対になるバグは i > 0 で逆順に数えることで、インデックス0に到達しません。List<T> で同じ間違いをすると、代わりに ArgumentOutOfRangeException が投げられます。

ループ中に要素を削除する

前から進むループの中で List<T> から削除すると、後ろの要素がすべて1つずつ前にずれるので、削除のたびにその直後の要素がループから飛ばされます。

出力:

forward:  5, 0, 3, 7
backward: 5, 3, 7

前から進むループは 0 を1つ残しました。インデックス1を削除した後、2つ目のゼロがインデックス1に移動しましたが、i++ はすでにインデックス2へ進んでいたからです。逆順のループなら、削除によってずれるのはすでに訪れた要素だけなので、この問題は起きません。1行で書く代替手段として、stock.RemoveAll(x => x == 0) でも同じことができます。foreach ループではそもそも削除が許されず、InvalidOperationException が投げられます。

浮動小数点のカウンター

等価性を判定する double のループカウンターは避けます。0.1 には正確な2進表現がないので、10回足しても正確に 1.0 にはなりません。

for (double x = 0; x != 1.0; x += 0.1)   // never ends: x skips past 1.0
{
}

代わりに整数で数え、そこから値を計算します:for (int i = 0; i <= 10; i++) { double x = i / 10.0; }。

forとforeachの比較

必要なこと使うもの
すべての要素を順に読むforeach
インデックス、または隣の要素(i - 1、i + 1)for
配列の要素に代入するfor
2つずつ進む、逆順に進むfor
ループ中に List から削除するfor、逆順
インデクサーのないコレクション(HashSet、Dictionary、LINQの結果)foreach
コレクションなしで何かをN回繰り返すfor

配列では、どちらのループも本質的に同じコードにコンパイルされるので、速度ではなく読みやすさで選びます。

よくある質問

C#のforループの構文は?

for (initializer; condition; iterator) { body } です。たとえば for (int i = 0; i < 5; i++) { ... } と書きます。初期化子は1回だけ実行され、条件は各反復の前に確認され、反復子は各反復の後に実行されます。条件が初めてfalseになったときにループが終わります。

C#で逆順にループするには?

最後のインデックスから始めて減らします:for (int i = items.Length - 1; i >= 0; i--)。> 0 ではなく >= 0 にします。そうしないと最初の要素が飛ばされます。逆順のループは、反復中に List から要素を削除する安全な方法でもあります。

C#でforeachではなくforを使うべきなのはどんなときですか?

インデックスが必要なとき(隣の要素との比較、配列への書き戻し、2つ以上の増分)、コレクションをたどるのではなく数を数えるとき、リストから要素を削除するときは for を使います。すべての要素を順に読むなら foreach を使います。短く書けて、間違えるインデックスもありません。

C#で無限ループを書くには?

for (;;) { ... } または while (true) { ... } です。for のヘッダーの3つの部分はすべて省略可能で、条件がない場合は真とみなされます。終了条件を満たしたら break か return でループから抜けます。

forループでIndexOutOfRangeExceptionが出るのはなぜですか?

条件がインデックスを長さの値まで到達させているからです。i <= array.Length は最後の要素の1つ先の array[array.Length] にアクセスします。有効なインデックスは 0 から Length - 1 までなので、条件は i < array.Length にします(List なら i < list.Count で、こちらは代わりに ArgumentOutOfRangeException を投げます)。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める