foreach ループの現在の反復を表す値を取得するために、C# で遭遇したことのないまれな言語構造 (最近学んだいくつか、スタック オーバーフローに関するものなど) はありますか?
たとえば、現在、状況に応じて次のようなことを行っています。
int i = 0;
foreach (Object o in collection)
{
// ...
i++;
}
Ian Mercer は、 Phil Haack のブログにこれと同様のソリューションを投稿しました。
foreach (var item in Model.Select((value, i) => new { i, value }))
{
var value = item.value;
var index = item.i;
}
これは、次の LINQ のオーバーロードを使用して、アイテム ( item.value
) とそのインデックス ( ) を取得します。item.i
Select
[Select 内] 関数の 2 番目のパラメーターは、ソース要素のインデックスを表します。
はnew { i, value }
新しい無名オブジェクトを作成しています。
ValueTuple
C# 7.0 以降を使用している場合は、次を使用してヒープ割り当てを回避できます。
foreach (var item in Model.Select((value, i) => ( value, i )))
{
var value = item.value;
var index = item.i;
}
item.
自動分解を使用して を排除することもできます。
foreach (var (value, i) in Model.Select((value, i) => ( value, i )))
{
// Access `value` and `i` directly here.
}
foreach
は、 を実装するコレクションを反復処理するためのものIEnumerable
です。GetEnumerator
を返すコレクションを呼び出すことでこれを行いEnumerator
ます。
この列挙子には、メソッドとプロパティがあります。
MoveNext()
Current
Current
列挙子が現在オンになっているオブジェクトを返し、次のオブジェクトにMoveNext
更新Current
します。
インデックスの概念は、列挙の概念とは無関係であり、実行できません。
そのため、ほとんどのコレクションは、インデクサーと for ループ コンストラクトを使用してトラバースできます。
この状況では、ローカル変数でインデックスを追跡するよりも for ループを使用する方がはるかに好きです。
foreach
最後に、C#7 には、ループ (つまりタプル)内でインデックスを取得するための適切な構文があります。
foreach (var (item, index) in collection.WithIndex())
{
Debug.WriteLine($"{index}: {item}");
}
ちょっとした拡張メソッドが必要になります:
public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)
=> self.Select((item, index) => (item, index));
次のようなことができます:
public static class ForEachExtensions
{
public static void ForEachWithIndex<T>(this IEnumerable<T> enumerable, Action<T, int> handler)
{
int idx = 0;
foreach (T item in enumerable)
handler(item, idx++);
}
}
public class Example
{
public static void Main()
{
string[] values = new[] { "foo", "bar", "baz" };
values.ForEachWithIndex((item, idx) => Console.WriteLine("{0}: {1}", idx, item));
}
}
for
ほとんどの場合、ループがより良い選択であるというコメントには同意しません。
foreach
は便利な構造でありfor
、すべての状況でループに置き換えることはできません。
たとえば、DataReaderがあり、 を使用してすべてのレコードをループすると、 Disposeforeach
メソッドが自動的に呼び出されてリーダーが閉じられます (その後、接続が自動的に閉じられます)。したがって、リーダーを閉じ忘れた場合でも接続リークを防ぐことができるため、より安全です。
(常にリーダーを閉じることは良い習慣ですが、そうしないとコンパイラはそれをキャッチしません - すべてのリーダーを閉じたことを保証することはできませんが、取得することで接続をリークしない可能性を高めることができますforeach を使用する習慣があります。)
Dispose
メソッドの暗黙的な呼び出しが役立つ例は他にもあるかもしれません。
int
文字通りの回答-警告、パフォーマンスは、インデックスを追跡するためにを使用する場合ほど良くない場合があります。少なくとも、を使用するよりはましですIndexOf
。
Selectのインデックス作成オーバーロードを使用して、コレクション内の各アイテムを、インデックスを認識している匿名オブジェクトでラップする必要があります。これは、IEnumerableを実装するすべてのものに対して実行できます。
System.Collections.IEnumerable collection = Enumerable.Range(100, 10);
foreach (var o in collection.OfType<object>().Select((x, i) => new {x, i}))
{
Console.WriteLine("{0} {1}", o.i, o.x);
}
カウンター変数を使用しても問題はありません。実際、 を使用する場合でも、 を使用する場合for
でもforeach
while
、do
カウンタ変数をどこかで宣言してインクリメントする必要があります。
したがって、適切にインデックス付けされたコレクションがあるかどうかわからない場合は、このイディオムを使用してください。
var i = 0;
foreach (var e in collection) {
// Do stuff with 'e' and 'i'
i++;
}
それ以外の場合、インデックス可能なコレクションがインデックスアクセス用に O(1) であることがわかっている場合は、これを使用します (これは、おそらく(ドキュメントには記載されていません) ためであり、他のタイプ ( など) には必ずしも必要ではありません):Array
List<T>
LinkedList
// Hope the JIT compiler optimises read of the 'Count' property!
for (var i = 0; i < collection.Count; i++) {
var e = collection[i];
// Do stuff with 'e' and 'i'
}
IEnumerator
を呼び出しMoveNext()
て質問することで を「手動で」操作する必要は決してないはずですCurrent
-foreach
その特定の手間を省きます...アイテムをスキップする必要がある場合continue
は、ループの本体で a を使用するだけです。
そして、完全を期すために、インデックスで何をしていたかに応じて(上記の構造体は十分な柔軟性を提供します)、Parallel LINQ を使用できます。
// First, filter 'e' based on 'i',
// then apply an action to remaining 'e'
collection
.AsParallel()
.Where((e,i) => /* filter with e,i */)
.ForAll(e => { /* use e, but don't modify it */ });
// Using 'e' and 'i', produce a new collection,
// where each element incorporates 'i'
collection
.AsParallel()
.Select((e, i) => new MyWrapper(e, i));
AsParallel()
すでに 2014 年なので、上記を使用します。これらの複数のコアをうまく利用して速度を上げたいと考えています。さらに、「シーケンシャル」LINQ の場合、拡張メソッドは onと...しか取得できForEach()
List<T>
Array
foreach
ません。これを使用する方が単純な.xml を実行するよりも優れているかどうかは明らかではありません。
@FlySwatの答えを使用して、私はこの解決策を思いつきました:
//var list = new List<int> { 1, 2, 3, 4, 5, 6 }; // Your sample collection
var listEnumerator = list.GetEnumerator(); // Get enumerator
for (var i = 0; listEnumerator.MoveNext() == true; i++)
{
int currentItem = listEnumerator.Current; // Get current item.
//Console.WriteLine("At index {0}, item is {1}", i, currentItem); // Do as you wish with i and currentItem
}
を使用して列挙子を取得し、GetEnumerator
ループを使用してfor
ループします。ただし、ループの条件を にするのがコツですlistEnumerator.MoveNext() == true
。
列挙子のMoveNext
メソッドは、次の要素がありアクセスできる場合に true を返すため、反復する要素がなくなるとループ条件がループを停止するようにします。
元の列挙子を、インデックス情報を含む別の列挙子でラップできます。
foreach (var item in ForEachHelper.WithIndex(collection))
{
Console.Write("Index=" + item.Index);
Console.Write(";Value= " + item.Value);
Console.Write(";IsLast=" + item.IsLast);
Console.WriteLine();
}
ForEachHelper
これがクラスのコードです。
public static class ForEachHelper
{
public sealed class Item<T>
{
public int Index { get; set; }
public T Value { get; set; }
public bool IsLast { get; set; }
}
public static IEnumerable<Item<T>> WithIndex<T>(IEnumerable<T> enumerable)
{
Item<T> item = null;
foreach (T value in enumerable)
{
Item<T> next = new Item<T>();
next.Index = 0;
next.Value = value;
next.IsLast = false;
if (item != null)
{
next.Index = item.Index + 1;
yield return item;
}
item = next;
}
if (item != null)
{
item.IsLast = true;
yield return item;
}
}
}
これが、この問題に対して私が思いついた解決策です
元のコード:
int index=0;
foreach (var item in enumerable)
{
blah(item, index); // some code that depends on the index
index++;
}
更新されたコード
enumerable.ForEach((item, index) => blah(item, index));
延長方法:
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumerable, Action<T, int> action)
{
var unit = new Unit(); // unit is a new type from the reactive framework (http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx) to represent a void, since in C# you can't return a void
enumerable.Select((item, i) =>
{
action(item, i);
return unit;
}).ToList();
return pSource;
}
なぜ foreach ?!
List を使用している場合、 foreach の代わりにforを使用するのが最も簡単な方法です。
for (int i = 0 ; i < myList.Count ; i++)
{
// Do something...
}
または、foreach を使用する場合:
foreach (string m in myList)
{
// Do something...
}
これを使用して、各ループのインデックスを知ることができます。
myList.indexOf(m)
リストに対してのみ機能し、IEnumerable に対しては機能しませんが、LINQ には次のようなものがあります。
IList<Object> collection = new List<Object> {
new Object(),
new Object(),
new Object(),
};
foreach (Object o in collection)
{
Console.WriteLine(collection.IndexOf(o));
}
Console.ReadLine();
@Jonathan私はそれが素晴らしい答えだとは言いませんでした。彼が求めたことを実行できることを示しているだけだと言いました:)
@Graphain私はそれが速いとは思っていません-それがどのように機能するか完全にはわかりません.一致するオブジェクトを見つけるために毎回リスト全体を繰り返しますが、これは非常に多くの比較になります.
とはいえ、List はカウントとともに各オブジェクトのインデックスを保持する場合があります。
ジョナサンはもっと良い考えを持っているようですが、詳しく説明してくれますか?
ただし、foreach のどこにいるのかをカウントしておく方が良いでしょう。よりシンプルで、より適応性があります。
これは私が行う方法であり、そのシンプルさ/簡潔さには優れていますが、ループ本体obj.Value
で多くのことを行っていると、かなり早く古くなります。
foreach(var obj in collection.Select((item, index) => new { Index = index, Value = item }) {
string foo = string.Format("Something[{0}] = {1}", obj.Index, obj.Value);
...
}
int index;
foreach (Object o in collection)
{
index = collection.indexOf(o);
}
これは、 をサポートするコレクションで機能しIList
ます。
このようなキーワードcontinue
セーフ構造を使用することをお勧めします
int i=-1;
foreach (Object o in collection)
{
++i;
//...
continue; //<--- safe to call, index will be increased
//...
}
次のようにループを記述できます。
var s = "ABCDEFG";
foreach (var item in s.GetEnumeratorWithIndex())
{
System.Console.WriteLine("Character: {0}, Position: {1}", item.Value, item.Index);
}
次の構造体と拡張メソッドを追加した後。
構造体と拡張メソッドは、Enumerable.Select 機能をカプセル化します。
public struct ValueWithIndex<T>
{
public readonly T Value;
public readonly int Index;
public ValueWithIndex(T value, int index)
{
this.Value = value;
this.Index = index;
}
public static ValueWithIndex<T> Create(T value, int index)
{
return new ValueWithIndex<T>(value, index);
}
}
public static class ExtensionMethods
{
public static IEnumerable<ValueWithIndex<T>> GetEnumeratorWithIndex<T>(this IEnumerable<T> enumerable)
{
return enumerable.Select(ValueWithIndex<T>.Create);
}
}
私はLINQPadでこれを構築しました:
var listOfNames = new List<string>(){"John","Steve","Anna","Chris"};
var listCount = listOfNames.Count;
var NamesWithCommas = string.Empty;
foreach (var element in listOfNames)
{
NamesWithCommas += element;
if(listOfNames.IndexOf(element) != listCount -1)
{
NamesWithCommas += ", ";
}
}
NamesWithCommas.Dump(); //LINQPad method to write to console.
次のように使用することもできますstring.join
。
var joinResult = string.Join(",", listOfNames);
参考までに、Phil Haack は Razor Templated Delegate のコンテキストでこの例を書きました ( http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx ) 。
彼は事実上、「IteratedItem」クラス (以下を参照) で反復をラップする拡張メソッドを作成し、反復中に要素だけでなくインデックスにもアクセスできるようにします。
public class IndexedItem<TModel> {
public IndexedItem(int index, TModel item) {
Index = index;
Item = item;
}
public int Index { get; private set; }
public TModel Item { get; private set; }
}
ただし、単一の操作 (つまり、ラムダとして提供できる操作) を実行している場合、これは非 Razor 環境では問題ありませんが、非 Razor コンテキストでの for/foreach 構文の確実な置き換えにはなりません。 .
これはあまり効率的ではないと思いますが、うまくいきます:
@foreach (var banner in Model.MainBanners) {
@Model.MainBanners.IndexOf(banner)
}
この問題に対する私の解決策は、拡張メソッドWithIndex()
です。
のように使う
var list = new List<int> { 1, 2, 3, 4, 5, 6 };
var odd = list.WithIndex().Where(i => (i.Item & 1) == 1);
CollectionAssert.AreEqual(new[] { 0, 2, 4 }, odd.Select(i => i.Index));
CollectionAssert.AreEqual(new[] { 1, 3, 5 }, odd.Select(i => i.Item));
質問に基づいてインデックス情報を使用して何をしようとしているのかわかりませんでした。ただし、C# では通常、IEnumerable.Select メソッドを適応させて、必要なものからインデックスを取得できます。たとえば、値が奇数か偶数かについて、このようなものを使用できます。
string[] names = { "one", "two", "three" };
var oddOrEvenByName = names
.Select((name, index) => new KeyValuePair<string, int>(name, index % 2))
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
これにより、項目がリスト内で奇数 (1) か偶数 (0) かの名前別の辞書が得られます。
コレクションが何らかの方法でオブジェクトのインデックスを返すことができない限り、唯一の方法は、例のようにカウンターを使用することです。
ただし、インデックスを操作する場合、この問題に対する唯一の合理的な答えは、for ループを使用することです。それ以外のものは、時間と空間の複雑さは言うまでもなく、コードの複雑さをもたらします。
foreach ループの現在の反復の値を取得する方法があるとは思いません。自分を数えることが最善の方法のようです。
お聞きしてもよろしいですか?なぜ知りたいのですか?
次の 3 つのいずれかを行っている可能性が高いようです。
1) コレクションからオブジェクトを取得しますが、この場合は既に取得しています。
2) 後で後処理するためにオブジェクトを数えます...コレクションには、利用できる Count プロパティがあります。
3)ループ内の順序に基づいてオブジェクトのプロパティを設定します...ただし、オブジェクトをコレクションに追加したときに簡単に設定できます。
私はちょうどこの問題を抱えていましたが、私の場合の問題を考えてみると、予想される解決策とは関係なく、最良の解決策が得られました。
基本的に、1 つのソース リストから読み取り、それらに基づいて宛先リストにオブジェクトを作成していますが、最初にソース項目が有効かどうかを確認し、任意の行を返す必要があります。エラー。一見すると、現在のプロパティでオブジェクトの列挙子にインデックスを取得したいのですが、これらの要素をコピーしているため、現在の宛先から暗黙的に現在のインデックスを知っています。明らかに、宛先オブジェクトに依存しますが、私にとってはリストであり、ICollection を実装する可能性が最も高いです。
すなわち
var destinationList = new List<someObject>();
foreach (var item in itemList)
{
var stringArray = item.Split(new char[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries);
if (stringArray.Length != 2)
{
//use the destinationList Count property to give us the index into the stringArray list
throw new Exception("Item at row " + (destinationList.Count + 1) + " has a problem.");
}
else
{
destinationList.Add(new someObject() { Prop1 = stringArray[0], Prop2 = stringArray[1]});
}
}
常に当てはまるわけではありませんが、多くの場合、言及する価値があると思います。
とにかく、ポイントは、あなたが持っているロジックにすでに自明ではない解決策がある場合があるということです...
コレクションがリストの場合、次のように List.IndexOf を使用できます。
foreach (Object o in collection)
{
// ...
@collection.IndexOf(o)
}
このようなものはどうですか?myEnumerable が空の場合、myDelimitedString は null になる可能性があることに注意してください。
IEnumerator enumerator = myEnumerable.GetEnumerator();
string myDelimitedString;
string current = null;
if( enumerator.MoveNext() )
current = (string)enumerator.Current;
while( null != current)
{
current = (string)enumerator.Current; }
myDelimitedString += current;
if( enumerator.MoveNext() )
myDelimitedString += DELIMITER;
else
break;
}
これは特定の質問には答えませんが、問題の解決策を提供します。for ループを使用してオブジェクト コレクションを実行します。次に、作業中の現在のインデックスが表示されます。
// Untested
for (int i = 0; i < collection.Count; i++)
{
Console.WriteLine("My index is " + i);
}