web-dev-qa-db-ja.com

C#foreachの改善?

Foreach内にループカウントインデックスを設定し、整数を作成し、それを使用し、インクリメントする必要があるプログラミング中に、私はこれに頻繁に遭遇しました。導入されたキーワードがあった場合、それは良い考えではないでしょうか。 foreach内のループ数?他のループでも使用できます。

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

9
Justin

foreachループ内でループカウントが必要な場合は、通常のforループを使用しないでください。 foreachループは、forループの特定の使用を簡単にすることを目的としています。 foreachの単純さはもはや有益ではない状況にあるようです。

54
Ryathal

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

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
37
Kris Harper

要求に応じて、ここに Dan Finchの回答 を追加しています。

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

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

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
} );
13
Jalayn

私はこれについて間違っているかもしれませんが、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ループの多くの場合、論理的な目的は反復とは異なります。

5
Jonathan Henson

問題のコレクションが_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++;
}
_
2
svick

コレクションが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()));
2
KeithS

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

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