foreach ループの現在の反復のインデックスを取得するにはどうすればよいでしょうか?

StackOverflow https://stackoverflow.com/questions/43021

  •  09-06-2019
  •  | 
  •  

質問

C# には、foreach ループの現在の反復を表す値を取得するための、これまでに遭遇したことのない珍しい言語構造 (最近学んだいくつかのスタック オーバーフローなど) はありますか?

たとえば、私は現在、状況に応じて次のようなことを行っています。

int i = 0;
foreach (Object o in collection)
{
    // ...
    i++;
}
役に立ちましたか?

解決

foreach を実装するコレクションを反復処理するためのものです IEnumerable. 。これは呼び出しによって行われます GetEnumerator コレクションに対して、 Enumerator.

この列挙子にはメソッドとプロパティがあります。

  • MoveNext()
  • 現在

Current Enumerator が現在存在するオブジェクトを返します。 MoveNext アップデート Current 次のオブジェクトへ。

インデックスの概念は列挙の概念とは異質であるため、列挙することはできません。

そのため、ほとんどのコレクションは、インデクサーと for ループ構造を使用して走査できます。

私は、ローカル変数を使用してインデックスを追跡するよりも、この状況で for ループを使用することを非常に好みます。

他のヒント

Ian Mercer がこれと同様のソリューションを投稿しました フィル・ハークのブログ:

foreach (var item in Model.Select((value, i) => new { i, value }))
{
    var value = item.value;
    var index = item.i;
}

これでアイテムが手に入ります(item.value) とそのインデックス (item.i) を使用して この Linq の過負荷 Select:

関数 [inside 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. 自動分割を使用すると、次のようになります。

<ol>
foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i )))
{
    <li id="item_@i">@value</li>
}
</ol>

次のようなことができます:

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

ついに C#7 には、オブジェクト内のインデックスを取得するための適切な構文が追加されました。 foreach ループ (i.e.タプル):

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)); 

私は、次のようなコメントには同意しません。 for ほとんどの場合、loop の方が良い選択です。

foreach は便利な構成要素ですが、 for あらゆる状況でループします。

たとえば、 データリーダー を使用してすべてのレコードをループします。 foreach 自動的に呼び出されます 廃棄 メソッドを実行し、リーダーを閉じます (その後、接続は自動的に閉じられます)。したがって、リーダーを閉じ忘れた場合でも接続漏れを防ぐことができるため、より安全です。

(確かに、常にリーダーを閉じるのは良い習慣ですが、そうしないとコンパイラーはそれを捕捉しません。すべてのリーダーを閉じたことを保証することはできませんが、 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);
}

@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 を返します。これにより、ループ条件により、反復する要素がなくなったときにループが停止します。

LINQ、C# 7、および System.ValueTuple NuGet パッケージでは、次のことができます。

foreach (var (value, index) in collection.Select((v, i)=>(v, i))) {
    Console.WriteLine(value + " is at index " + index);
}

通常のを使用できます foreach を構築し、オブジェクトのメンバーとしてではなく値とインデックスに直接アクセスできるようにし、両方のフィールドをループのスコープ内にのみ保持します。これらの理由から、C# 7 を使用できる場合、これが最良のソリューションであると考えています。 System.ValueTuple.

元の列挙子を、インデックス情報を含む別の列挙子でラップすることができます。

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;
        }            
    }
}

カウンタ変数を使用することに問題はありません。実際、使用するかどうかにかかわらず、 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 ループの本体内。

完全を期すために、あなたが何であったかにもよりますが、 やってる インデックスを使用して (上記の構造は十分な柔軟性を提供します)、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 の場合、 あなただけが得られます ForEach() 拡張メソッド List<T> そして Array ...そして、それを使用することが、単純な操作を行うよりも優れているかどうかは明らかではありません。 foreach, 、より醜い構文のためにまだシングルスレッドで実行しているためです。

この問題に対して私が思いついた解決策は次のとおりです

元のコード:

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;
    }
int index;
foreach (Object o in collection)
{
    index = collection.indexOf(o);
}

これは、サポートしているコレクションで機能します。 IList.

C# 7 では、ついにこれを行うためのエレガントな方法が提供されました。

static class Extensions
{
    public static IEnumerable<(int, T)> Enumerate<T>(
        this IEnumerable<T> input,
        int start = 0
    )
    {
        int i = start;
        foreach (var t in input)
        {
            yield return (i++, t);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var s = new string[]
        {
            "Alpha",
            "Bravo",
            "Charlie",
            "Delta"
        };

        foreach (var (i, t) in s.Enumerate())
        {
            Console.WriteLine($"{i}: {t}");
        }
    }
}

これは List に対してのみ機能し、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();

@ジョナサン私はそれが素晴らしい答えだとは言いませんでした、彼が尋ねたことを実行することが可能であることを示しているだけだと言いました:)

@Graphainそれが高速であるとは期待していません。それがどのように機能するか完全にはわかりませんが、一致するオブジェクトを見つけるために毎回リスト全体を反復処理する可能性があり、これは膨大な比較になります。

つまり、List はカウントとともに各オブジェクトのインデックスを保持する可能性があります。

ジョナサンはもっと良いアイデアを持っているようですが、詳しく説明していただけますか?

ただし、foreach のどこまで行っているかをカウントしておくほうが、よりシンプルで順応性が高くなります。

独自のインデックスを追加するだけです。単純にする。

int i = 0;
foreach (var item in Collection)
{
    item.index = i;
    ++i;
}

これが私のやり方です。そのシンプルさと簡潔さの点では優れていますが、ループ本体で多くのことを実行している場合は、 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);
    ...
}

なぜフォーリーチするのか?

最も簡単な方法は、 のために foreachの代わりに リストを使用している場合 .

for(int i = 0 ; i < myList.Count ; i++)
{
    // Do Something...
}

または、 foreach を使用したい場合:

foreach (string m in myList)
{
     // Do something...       
}

これを使用して、各ループのインデックスを知ることができます。

myList.indexOf(m)

キーワードを使った方が良い continue このような安全な構造

int i=-1;
foreach (Object o in collection)
{
    ++i;
    //...
    continue; //<--- safe to call, index will be increased
    //...
}

コレクションがリストの場合は、次のように List.IndexOf を使用できます。

foreach (Object o in collection)
{
    // ...
    @collection.IndexOf(o)
}

主な答えは次のように述べています。

「明らかに、インデックスの概念は列挙の概念とは異質であり、実行できません。」

これは現在の C# バージョンにも当てはまりますが、これは概念的な制限ではありません。

MS による新しい C# 言語機能の作成と、新しいインターフェイス IIndexedEnumerable のサポートにより、この問題を解決できる可能性があります。

foreach (var item in collection with var index)
{
    Console.WriteLine("Iteration {0} has value {1}", index, item);
}

//or, building on @user1414213562's answer
foreach (var (item, index) in collection)
{
    Console.WriteLine("Iteration {0} has value {1}", index, item);
}

foreach に IEnumerable が渡され、IIndexedEnumerable を解決できないが、var インデックスで要求された場合、C# コンパイラはソースを IndexedEnumerable オブジェクトでラップし、インデックスを追跡するためのコードを追加できます。

interface IIndexedEnumerable<T> : IEnumerable<T>
{
    //Not index, because sometimes source IEnumerables are transient
    public long IterationNumber { get; }
}

なぜ:

  • Foreach は見た目が良く、ビジネス アプリケーションではパフォーマンスのボトルネックになることはほとんどありません
  • Foreach はメモリ効率を高めることができます。各ステップで新しいコレクションに変換するのではなく、関数のパイプラインを使用します。使用する CPU サイクルがもう少し多くても、CPU キャッシュの障害や GC.Collects が少なくなれば、誰が気にするでしょうか。
  • コーダーにインデックス追跡コードを追加する必要があるため、美しさが損なわれます
  • 実装は非常に簡単で (MS に感謝)、下位互換性があります。

ここにいるほとんどの人は MS ではありませんが、これは正しい答えです。MS にそのような機能を追加するよう働きかけることができます。すでに独自のイテレータを構築できます。 拡張関数とタプルの使用, ただし、MS は拡張関数を回避するために構文上のシュガーを振りかけることができます。

この問題に対する私の解決策は拡張メソッドです WithIndex(),

http://code.google.com/p/ub-dotnet-utilities/source/browse/trunk/Src/Utilities/Extensions/EnumerableExtensions.cs

のように使用してください

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));

参考までに、Phil Haack が Razor テンプレート デリゲートのコンテキストでこの例を書きました (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)
}

これを組み込みました LINQパッド:

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);

ループは次のように記述できます。

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

foreach ループの現在の反復値を取得する方法はないと思います。自分自身を数えることが最善の方法のようです。

なぜ知りたいのか聞いてもいいですか?

おそらく次の 3 つのうちの 1 つを行うことになると思われます。

1) コレクションからオブジェクトを取得します。ただし、この場合はすでにオブジェクトを持っています。

2) 後の後処理のためにオブジェクトをカウントします...コレクションには、使用できる Count プロパティがあります。

3) ループ内の順序に基づいてオブジェクトのプロパティを設定します。ただし、オブジェクトをコレクションに追加するときに簡単に設定できます。

コレクションが何らかのメソッドを介してオブジェクトのインデックスを返すことができない場合、唯一の方法は例のようにカウンターを使用することです。

ただし、インデックスを使用する場合、問題に対する唯一の合理的な答えは、for ループを使用することです。それ以外のものは、時間と空間の複雑さは言うまでもなく、コードの複雑さをもたらします。

こんな感じでどうでしょうか?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;
}

この問題が発生したばかりですが、私の場合の問題をよく考えてみると、予想される解決策とは関係なく、最良の解決策が得られました。

これは非常に一般的なケースかもしれません。基本的に、1 つのソース リストから読み取り、それに基づいて宛先リストにオブジェクトを作成していますが、最初にソース項目が有効かどうかを確認する必要があり、任意の行を返したいと考えています。エラー。一見すると、Current プロパティでオブジェクトの列挙子のインデックスを取得したいと考えていますが、これらの要素をコピーしているため、現在のインデックスは現在のコピー先から暗黙的にわかっています。明らかに、それは宛先オブジェクトに依存しますが、私にとってはリストであり、おそらく 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]});
  }
}

常に当てはまるわけではありませんが、言及する価値がある場合は十分にあると思います。

とにかく、重要なのは、時々、あなたが持っているロジックの中に、自明ではない解決策がすでに存在しているということです...

質問に基づいてインデックス情報を使用して何をしようとしているのかわかりませんでした。ただし、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) かを示す名前の辞書が得られます。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top