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) は決まった順序で実行されます。
- 初期化子(
int i = 1)が、何よりも先に1回だけ実行されます。 - 条件(
i <= 5)が確認されます。falseならループは終わり、本体は二度と実行されません。 - 本体が実行されます。
- 反復子(
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 を投げます)。