C#foreachの改善?


9

foreach内にループカウントインデックスを設定し、整数を作成し、それを使用し、インクリメントする必要がある場合、プログラミング中にこれに頻繁に出くわします。 foreach内のループ数?他のループでも使用できます。

これは、コンパイラーがこのキーワードをループ構造内以外のどこで使用することをどのように許可しないかというキーワードの使用に反しますか?


7
あなたは、拡張メソッドでそれを自分で行うダン・フィンチからの回答を参照することができますstackoverflow.com/a/521894/866172(ない最高の定格の答えを、私はそれが「クリーン」1だと思う)
Jalayn

Perlや$ _のようなものが頭に浮かびます。私はそれが嫌いです。
Yam Marcovic 2012年

2
私がC#について知っていることから、これには多くのコンテキストベースのキーワードがあるため、これは「キーワードの使用に反対する」ことはありません。ただし、以下の回答で指摘されているように、おそらくfor () ループを使用するだけです。
Craige

@Jalayn回答として投稿してください。これは最良のアプローチの1つです。
Apoorv Khurasia

@MonsterTruck:私は彼がそうすべきではないと思います。実際の解決策は、この質問をSOに移行し、Jalaynがリンクする質問の複製として閉じることです。
Brian

回答:


54

ループ内でループカウントが必要な場合foreachは、通常のforループを使用しないでください。foreachループは、特定の用途にすることを意図されたforシンプルなループを。のシンプルさがforeachもはや有益ではない状況があるようです。


1
+1-良い点。IEnumerable.Countまたはobject.lengthを通常のforループで使用して、オブジェクト内の要素の数を把握する際の問題を回避するのは簡単です。

11
@ GlenH7:実装によっては、IEnumerable.Count高価になる可能性があります(または、1回だけの列挙の場合は不可能です)。安全にそれを行う前に、基礎となるソースが何かを知る必要があります。
Binary Worrier 2012年

2
-1:より効果的なC#の Bill Wagnerは、なぜ常にこだわるべきかを説明しforeachていfor (int i = 0…)ます。彼は数ページ書いていますが、私は2つの言葉で要約できます-反復時のコンパイラー最適化。それは2語ではありませんでした。
Apoorv Khurasia

13
@MonsterTruck:ビルワグナーが書いたものは何でも、forループがより読みやすいコードを提供するOPで説明されているような十分な状況を見てきました(そして理論的なコンパイラの最適化は、多くの場合、時期尚早な最適化です)。
ドクターブラウン

1
@DocBrownこれは時期尚早の最適化ほど単純ではありません。私は過去にボトルネックを特定し、このアプローチを使用して修正しました(ただし、コレクション内の何千ものオブジェクトを処理していたことは認めます)。実用的な例をすぐに投稿してください。その間、これがジョン・スキートです。[パフォーマンスの向上はそれほど大きくないが、それはコレクションとオブジェクトの数に大きく依存すると彼は言う]
Apoorv Khurasia

37

このような匿名型を使用できます

foreach (var item in items.Select((v, i) => new { Value = v, Index = i }))
{
    Console.WriteLine("Item value is {0} and index is {1}.", item.Value, item.Index);
}

これはPythonのenumerate関数に似ています

for i, v in enumerate(items):
    print v, i

+1ああ...電球。私はそのアプローチが好きです。なぜ今まで考えたことがないのですか?
Phil

17
従来のforループよりもC#でこれを好む人は誰でも、読みやすさについて奇妙な意見を持っています。これはきちんとしたトリックかもしれませんが、プロダクション品質のコードではそのようなことを許可しません。これは、従来のforループよりもはるかにノイズが多く、速く理解することができません。
Falcon

4
@Falcon、これは古いカウントのループ(カウントが必要)を使用できない場合に有効な代替手段です。それは私が回避しなければならなかったいくつかのオブジェクトのコレクションです...
Fabricio Araujo '14

8
@FabricioAraujo:確かにC#のforループはカウントを必要としません。私の知る限り、これらはCのforループとまったく同じです。つまり、任意のブール値を使用してループを終了できます。MoveNextがfalseを返したときの列挙の終わりのチェックなど。
Zan Lynx

1
これには、すべてのコードでforeachブロックの開始時にインクリメントを処理する必要がなく、インクリメントを処理できるという追加の利点があります。
JeffO 2016年

13

リクエストに応じて、ダンフィンチの答えをここに追加します。

私にポイントを与えないでください、ダン・フィンチにポイントを与えてください:-)

解決策は、次の一般的な拡張メソッドを記述することです。

public static void Each<T>( this IEnumerable<T> ie, Action<T, int> action )
{
    var i = 0;
    foreach ( var e in ie ) action( e, i++ );
}

これは次のように使用されます:

var strings = new List<string>();
strings.Each( ( str, n ) =>
{
    // hooray
} );

2
それはかなり賢いですが、特にコードを保守することになる人が.NETのエースではなく、拡張メソッドの概念にあまり精通していない可能性がある場合は特に、通常のforを使用する方が「読みやすい」と思います。 。私はまだこの少しのコードをつかんでいます。:)
MetalMikester 2012年

1
これのどちらかの側と最初の質問を議論することができます-私はポスターの意図に同意します、foreachはよりよく読むがインデックスが必要な場合がありますが、そうでない場合、私は常により多くの構文糖を言語に追加することに不利ですひどく必要-私は自分のコードにまったく同じ解決策を別の名前で持っています-strings.ApplyIndexed <T>(......)
Mark Mullin

私はマーク・マリンに同意します、どちら側でも議論できます。私はそれが拡張メソッドのかなり賢い使い方だと思い、それが質問に合うのでそれを共有しました。
Jalayn

5

私はこれについて間違っているかもしれませんが、foreachループの主なポイントはC ++のSTL日からの反復を単純化することだといつも思っていました。

for(std::stltype<typename>::iterator iter = stl_type_instance.begin(); iter != stl_type_instance.end(); iter++)
{
   //dereference iter to use each object.
}

これをforeachでの.NETのIEnumerableの使用と比較してください。

foreach(TypeName instance in DescendentOfIEnumerable)
{
   //use instance to use the object.
}

後者ははるかに単純で、古い落とし穴の多くを回避します。さらに、彼らはほとんど同じルールに従っているようです。たとえば、ループ内ではIEnumerableの内容を変更することはできません(これをSTLの方法で考えると、はるかに理にかなっています)。ただし、forループの多くの場合、論理的な目的は反復とは異なります。


4
+1:foreachは基本的にイテレーターパターンの構文糖であることを覚えている人はほとんどいません。
Steven Evers

1
まあ、いくつかの違いがあります。:.NETに存在するが、プログラマからかなり深く隠されていますが、非常に多くのC ++の例のように見えたforループを書くことができポインタ操作for(IEnumerator e=collection.GetEnumerator;e.MoveNext();) { ... }
KeithS

@KeithS、構造化タイプではない.NETで触れるすべてのものは、異なる構文(->ではなく。実際、参照型をメソッドに渡すことは、それをポインタとして渡し、それを参照渡しすることは、それをダブルポインタとして渡すことと同じです。同じこと。少なくとも、それはC ++を母国語とする人がそれについて考える方法です。一種のように、あなたは言語があなた自身の母国語に構文的にどのように関連しているかについて考えることによって学校でスペイン語を学びます。
ジョナサンヘンソン

*値型は構造型ではありません。ごめんなさい。
ジョナサンヘンソン

2

問題のコレクションがである場合は、IList<T>単にforインデックス付けで使用できます。

for (int i = 0; i < collection.Count; i++)
{
    var item = collection[i];
    // …
}

そうでない場合は、を使用できますSelect()。インデックスを提供するオーバーロードがあります。

それも適切でない場合、手動でインデックスを維持することは非常に簡単であると思います。これは非常に短い2行のコードです。このためのキーワードを作成するのはやり過ぎです。

int i = 0;
foreach (var item in collection)
{
    // …
    i++;
}

+1、また、ループ内で1回だけインデックスを使用している場合、必要なインデックスのように、foo(++i)またはそれにfoo(i++)応じて、何かを実行できます。
Job

2

コレクションがIEnumerableであることがわかっているが、これまでに処理した要素の数(つまり、完了したときの合計)を追跡する必要がある場合は、基本的なforループに数行追加できます。

var coll = GetMyCollectionAsAnIEnumerable();
var idx = 0;
for(var e = coll.GetEnumerator(); e.MoveNext(); idx++)
{
   var elem = e.Current;

   //use elem and idx as you please
}

また、インクリメントされたインデックス変数をforeachに追加することもできます。

var i=0;
foreach(var elem in coll)
{
   //do your thing, then...
   i++;
}

これをよりエレガントに見せたい場合は、1つまたは2つの拡張メソッドを定義して、これらの詳細を「隠す」ことができます。

public static void ForEach<T>(this IEnumerable<T> input, Action<T> action)
{
    foreach(T elem in input)
        action(elem);
}

public static void ForEach<T>(this IEnumerable<T> input, Action<T, int> action)
{
    var idx = 0;
    foreach(T elem in input)
        action(elem, idx++); //post-increment happens after parameter-passing
}

//usage of the index-supporting method
coll.ForEach((e, i)=>Console.WriteLine("Element " + (i+1) + ": " + e.ToString()));

1

他の名前との衝突をブロックから排除したい場合は、スコープも機能します...

{ int index = 0; foreach(var el in List) { Console.WriteLine(el + " @ " + index++); } }

-1

これにはwhileループを使用します。ループforも機能し、多くの人々はそれらを好むようですが、私forは固定された反復回数を持つものでのみ使用するのが好きです。IEnumerablesは実行時に生成される可能性があるので、私の考えでwhileはより理にかなっています。必要に応じて、非公式のコーディングガイドラインと呼んでください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.