web-dev-qa-db-ja.com

ループでfor(int i = 0; i <length; i ++)の代わりにforeachを使用する必要があるのはなぜですか?

C#とJavaでループするクールな方法は、ループにCスタイルの代わりにforeachを使用することです。

Cスタイルよりもこのスタイルを好むべき理由はありますか?

これら2つのケースに特に興味がありますが、ポイントを説明するために必要な数のケースに対処してください。

  • リスト内の各アイテムに対して操作を実行したい。
  • リスト内のアイテムを検索していますが、そのアイテムが見つかったら終了したいです。
45
MedicineMan

私が考えることができる2つの主な理由は次のとおりです。

1)基になるコンテナタイプから抽象化します。これは、たとえば、コンテナを変更するときにコンテナ内のすべてのアイテムをループするコードを変更する必要がないことを意味します-goal of "do this for meansではなく、コンテナ内のすべてのアイテム」。

2)オフバイワンエラーの可能性を排除します。

リスト内の各アイテムに対して操作を実行するという点では、次のように言うのは直感的です。

for(Item item: lst)
{
  op(item);
}

反復子を使用して手動で行うのではなく、読者に意図を完全に表現します。アイテムを検索するための同上。

71
Stuart Golodetz

あなたがレストランの主任シェフであり、ビュッフェ用に巨大なオムレツを準備していると想像してください。あなたは、1ダースの卵のカートンを2人のキッチンスタッフのそれぞれに渡し、文字通りひびを入れるように伝えます。

最初のボウルはボウルを設置し、木枠を開き、各卵を順番につかみます-左から右へ、上列、次に下列で-ボウルの側面にぶつけてから、ボウルに入れます。最終的に彼は卵を使い果たします。よくできた仕事。

2つ目はボウルを設置し、木枠を開き、ダッシュで一枚の紙とペンを手に入れます。彼は、卵のカートンのコンパートメントの横に0から11までの数字と、紙の上に0を書きます。彼は紙の番号を見て、0というラベルの付いたコンパートメントを見つけ、卵を取り除き、ボウルに割ります。彼は再び紙の0を見て、「0 + 1 = 1」と考え、0を取り消し、紙に1を書き込みます。彼はコンパートメント1から卵をつかみ、割れます。といった具合に、12番が紙に載り、卵がもうないことを(見ずに!)知っているまで。よくできた仕事。

2番目の男の頭が少し台無しになったと思いますか?

高水準言語で作業することのポイントは、コンピューターの用語で物事を記述する必要を避け、自分の用語でそれらを記述することができるようにすることです。言語が高レベルであるほど、これは真実です。ループ内でカウンターをインクリメントすると、本当にやりたいこと、つまり各要素を処理することから気が散ることになります。


それに加えて、リンクリスト型の構造は、カウンターをインクリメントしてインデックスを作成しても効率的に処理できません。「インデックス作成」とは、カウントを最初からやり直すことを意味します。 Cでは、ループ「カウンター」へのポインターを使用してそれを逆参照することにより、自分で作成したリンクリストを処理できます。 「イテレータ」を使用して、これを現代のC++(およびC#とJavaである程度)で行うことができますが、これはまだ間接性の問題を抱えています。


最後に、一部の言語は、実際にループを記述するが「リスト内の各項目に対して操作を実行する」または「リスト内の項目を検索する」という考えがぞっとするほど高いレベルですヘッドシェフが最初のキッチンスタッフにすべての卵が割れていることを確認する方法を伝える必要がないのと同じ方法)。そのループ構造を設定する関数が提供されており、検索の場合、高次関数、または場合によっては比較値を使用して、ループ内で何をするかを指示します。 (実際、これらのことはC++で行うことができますが、インターフェースはやや不格好です。)

78
Karl Knechtel
  • foreachはよりシンプルで読みやすい

  • リンクされたリストのような構造に対してより効率的です

  • すべてのコレクションがランダムアクセスをサポートしているわけではありません。 HashSet<T>またはDictionary<TKey, TValue>.KeysCollectionを反復処理する唯一の方法はforeachです。

  • foreachを使用すると、追加の一時変数なしでメソッドから返されたコレクションを反復処理できます。

    foreach(var thingy in SomeMethodCall(arguments)) { ... }
    
37
SLaks

私にとっての利点の1つは、次のような間違いを犯しにくいことです。

    for(int i = 0; i < maxi; i++) {
        for(int j = 0; j < maxj; i++) {
...
        }
    }

更新:これはバグが発生する1つの方法です。合計する

int sum = 0;
for(int i = 0; i < maxi; i++) {
    sum += a[i];
}

その後、さらに集約することを決定します。そこで、ループを別のループでラップします。

int total = 0;
for(int i = 0; i < maxi; i++) {
   int sum = 0;
    for(int i = 0; i < maxi; i++) {
        sum += a[i];
    }
    total += sum;
}

コンパイルはもちろん失敗するので、手作業で編集します

int total = 0;
for(int i = 0; i < maxi; i++) {
   int sum = 0;
    for(int j = 0; j < maxj; i++) {
        sum += a[i];
    }
    total += sum;
}

現在、コードには少なくとも2つの誤りがあり(maxiとmaxjを混乱させた場合はそれ以上)、実行時エラーによってのみ検出されます。そして、あなたがテストを書かないなら...そしてそれはまれなコードです-これは誰か他の人を噛むでしょう-ひどく。

そのため、内部ループをメソッドに抽出することをお勧めします。

int total = 0;
for(int i = 0; i < maxi; i++) {
    total += totalTime(maxj);
}

private int totalTime(int maxi) {
   int sum = 0;
    for(int i = 0; i < maxi; i++) {
        sum += a[i];
    }
    return sum;
}

より読みやすくなりました。

19

foreachは、説明したような単純なシナリオを含め、すべてのシナリオ[1]でforと同じように機能します。

ただし、foreachには、forよりもパフォーマンスに関連しない特定の利点があります。

  1. 利便性。余分なローカルiを保持する必要はありません(これは、ループを促進する以外の目的はありません)。また、現在の値を変数にフェッチする必要はありません。ループ構造はすでにそれを処理しました。
  2. 一貫性foreachを使用すると、配列ではないシーケンスを簡単に繰り返すことができます。 forを使用して非配列順序シーケンス(たとえば、マップ/ディクショナリ)をループするには、少し異なる方法でコードを記述する必要があります。 foreachは、すべてのケースで同じです。
  3. 安全性。大きな力には大きな責任が伴います。そもそもループ変数が必要ないのに、なぜループ変数のインクリメントに関連するバグの機会を開くのですか?

したがって、ご覧のとおり、foreachmostの状況で使用するのに「より良い」です。

ただし、他の目的でiの値が必要な場合、または配列であることがわかっているデータ構造を処理している場合(および配列である実際の特定の理由がある場合)、より詳細なforが提供する機能が進むべき道です。

[1]「すべてのシナリオで」とは、実際には「コレクションが反復されやすいすべてのシナリオ」を意味し、実際には「ほとんどのシナリオ」になります(以下のコメントを参照)。ただし、反復に適さないコレクションを含む反復シナリオを設計する必要があると思います。

16
Jon

Stuart Golodetzが答えたように、それは抽象化です。

iの値を他の目的に使用するのではなく、iをインデックスとしてのみ使用している場合

   String[] lines = getLines();
   for( int i = 0 ; i < 10 ; ++i ) {
      System.out.println( "line " + i + lines[i] ) ;
   }

iの現在の値を知る必要はありません。エラーが発生する可能性があるだけです。

   Line[] pages = getPages();
   for( int i = 0 ; i < 10 ; ++i ) {
        for( int j = 0 ; j < 10 ; ++i ) 
             System.out.println( "page " + i + "line " + j + page[i].getLines()[j];
   }

アンドリュー・ケーニッヒが言うように、「抽象化は選択的な無知」です。コレクションの反復方法の詳細を知る必要がない場合は、それらの詳細を無視する方法を見つければ、より堅牢なコードを作成できます。

6
tpdi

Foreachを使用する理由:

  • ループの誤動作を引き起こす可能性のあるエラー(たとえば、forループでi++を忘れた)が忍び寄るのを防ぎます。ループを台無しにする方法はたくさんありますが、foreachループを台無しにする方法は多くありません。
  • ずっときれいに見えます/わかりにくいです。
  • forループは、場合によっては不可能な場合もあります(たとえば、IEnumerable<T>のようにインデックス付けできないIList<T>がある場合)。

forを使用する理由:

  • これらの種類のループは、列挙子を使用して作成される追加レベルの間接参照がないため、フラットリスト(配列)を反復処理する際にわずかなパフォーマンス上の利点があります。 (ただし、このパフォーマンスの向上はごくわずかです。)
  • 列挙したいオブジェクトはIEnumerable<T>を実装しません-foreachは列挙可能なものに対してのみ動作します。
  • その他の特殊な状況。たとえば、ある配列から別の配列にコピーする場合、foreachは、宛先配列スロットのアドレス指定に使用できるインデックス変数を提供しません。 forは、そのような場合に意味のある唯一のものです。

質問でリストする2つのケースは、どちらのループを使用しても実質的に同じです。最初のループではリストの最後まで繰り返し、2番目のループではアイテムが見つかったらbreak;あなたは探している。

foreachをさらに説明するために、このループ:

IEnumerable<Something> bar = ...;

foreach (var foo in bar) {
    // do stuff
}

以下の構文糖衣です:

IEnumerable<Something> bar = ...;

IEnumerator<Something> e = bar.GetEnumerator();
try {
    Something foo;
    while (e.MoveNext()) {
        foo = e.Current;
        // do stuff
    }
} finally {
    ((IDisposable)e).Dispose();
}
4
cdhowie

IEnumerableを実装するコレクションを反復処理する場合は、foreachを使用する方が自然です。反復の次のメンバーは、最後に到達するためのテストが完了すると同時に割り当てられるためです。例えば。、

 foreach (string day in week) {/* Do something with the day ... */}

より簡単です

for (int i = 0; i < week.Length; i++) { day = week[i]; /* Use day ... */ }

クラスのIEnumerableの実装でforループを使用することもできます。 GetEnumerator()の実装に、ループの本文でC#yieldキーワードを使用させるだけです。

yield return my_array[i];
3
Buggieboy

Javaには、指定した両方のループタイプがあります。必要に応じて、いずれかのforループバリアントを使用できます。あなたのニーズはこんな感じです

  1. リスト内の検索項目のインデックスを再実行します。
  2. アイテム自体を取得したい。

最初のケースでは、古典的な(cスタイル)forループを使用する必要があります。ただし、2番目の場合は、foreachループを使用する必要があります。

foreachループは、最初のケースでも使用できます。ただし、その場合は独自のインデックスを維持する必要があります。

2

いくつかの理由が考えられます

  1. 君は can't mess up indexes、モバイル環境でもコンパイラーの最適化がなく、ループに対して大雑把に書かれているため、各ループは1のみを行うため、いくつかの境界チェックを行うことができます。
  2. 君は can't change data input size(要素の追加/削除)を繰り返します。あなたのコードはそれを簡単に壊しません。データをフィルタリングまたは変換する必要がある場合は、他のループを使用します。
  3. インデックスによるアクセスはできませんが、クロールすることができるデータ構造を反復処理できます。それぞれの必要に応じて、実装するiterable interface(Java)またはIEnumerable(c#)を拡張します。
  4. より小さいサイズにすることができますboiler plate、たとえばXMLを解析する場合、SAXとStAXの違いは、最初に要素を参照するDOMのメモリ内コピーが必要です。

リストごとにそれぞれのアイテムを検索している場合、ほとんどの場合、間違って検索していることに注意してください。一緒に検索をスキップするには、hashmapまたはbimapの使用を検討してください。

プログラマーがfor loop なので for eachイテレータを使用すると、要素をスキップするという一般的なバグが存在します。そのため、そのシーンではより安全です。

for ( Iterator<T> elements = input.iterator(); elements.hasNext(); ) {
   // Inside here, nothing stops programmer from calling `element.next();` 
   // more then once.
} 
1
Margus

少なくともJavaでforeachを使用しない理由の1つは、最終的にガベージコレクションされるイテレータオブジェクトを作成することです。したがって、ガベージコレクションを回避するコードを作成する場合は、ただし、イテレータを作成しないため、純粋な配列の場合は問題ありません。

1
John Schroder

foreachで必要なことを実行できる場合は、それを使用します。そうでない場合-たとえば、何らかの理由でインデックス変数自体が必要な場合-forを使用します。シンプル!

(そして、あなたの2つのシナリオは、forまたはforeachのどちらでも同様に可能です。)

1
LukeH

foreachは、重いコレクションを実装するために桁違いに遅くなります。

証拠があります。これらは私の発見です

次の簡単なプロファイラーを使用して、パフォーマンスをテストしました

static void Main(string[] args)
{
    DateTime start = DateTime.Now;
    List<string> names = new List<string>();
    Enumerable.Range(1, 1000).ToList().ForEach(c => names.Add("Name  = " + c.ToString()));
    for (int i = 0; i < 100; i++)
    {
        //For the for loop. Uncomment the other when you want to profile foreach loop
        //and comment this one
        //for (int j = 0; j < names.Count; j++)
          //  Console.WriteLine(names[j]);
        //for the foreach loop
        foreach (string n in names)
        {
            Console.WriteLine(n);
        }
    }
    DateTime end = DateTime.Now;
    Console.WriteLine("Time taken = " + end.Subtract(start).TotalMilliseconds + " milli seconds");

そして、私は次の結果を得ました

所要時間= 11320.73ミリ秒(ループ用)

所要時間= 11742.3296ミリ秒(foreachループ)

0
sam

ほとんどのアイテムがカバーされているように見えます...以下は、特定の質問に関して言及されていない追加のメモです。これらは、スタイル設定とは対照的に、厳しいルールです。

リスト内の各アイテムに対して操作を実行したい

foreachループでは、反復変数の値を変更できません。したがって、リスト内の特定のアイテムの値を変更する場合は、forを使用する必要があります。

また、「クール」な方法はLINQを使用することです。興味があれば検索できるリソースがたくさんあります。

0
John Rasch

きれいなコードについて言えば、foreachステートメントはforステートメントよりもずっと読みやすいです!

Linq(C#)はほぼ同じことができますが、初心者の開発者はそれらを読むのに苦労する傾向があります!

0
user523650