foreachループの現在の反復のインデックスをどのように取得しますか?


939

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

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

int i = 0;
foreach (Object o in collection)
{
    // ...
    i++;
}

1
foreachキャストの取得は、通常、コレクションに対してインデックスベースのアクセスを使用するよりも最適化されていませんが、多くの場合は同じです。foreachの目的は、コードを読みやすくすることですが、(通常)間接層を追加しますが、これは無料ではありません。
ブライアン

8
の主な目的foreachは、インデックス付け可能かどうかに関係なく、すべてのコレクションに共通の反復メカニズムを提供することですListDictionary)。
ブライアンギデオン

2
こんにちはブライアンギデオン-間違いなく同意します(これは数年前のことで、当時は私はあまり経験がありませんでした)。ただし、Dictionaryはインデックス化できませんが、の反復はDictionary特定の順序でトラバースします(つまり、列挙子は要素を順次生成するという事実によってインデックス化できます)。この意味では、コレクション内のインデックスではなく、列挙内の現在の列挙要素のインデックス(つまり、最初または5番目、最後の列挙要素のいずれにあるか)を探していると言えます。
Matt Mitchell、

4
また、foreachにより、コンパイラーは、コンパイルされたコード内の各配列アクセスをチェックする境界をスキップできます。インデックスでforを使用すると、ランタイムがインデックスへのアクセスが安全かどうかをチェックします。
IvoTops 2012

1
しかし、それは誤りです。ループ内でforループの反復変数を変更しない場合、コンパイラーはその境界を認識しており、それらを再度チェックする必要はありません。これは非常に一般的なケースで、適切なコンパイラはそれを実装します。
ジムバルター2013年

回答:


552

foreach実装のコレクションを反復処理するためにありますIEnumerable。これを行うGetEnumeratorには、コレクションを呼び出します。Enumeratorます。

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

  • MoveNext()
  • 電流

CurrentEnumeratorが現在オンになっているオブジェクトを返し、MoveNext更新しますCurrent次のオブジェクトにします。

インデックスの概念は、列挙の概念とは無関係であり、実行できません。

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

この状況では、ローカル変数を使用してインデックスを追跡するよりも、forループを使用することを強くお勧めします。


165
「明らかに、インデックスの概念は列挙の概念とは無関係であり、実行することはできません。」-David Bとbcahillの回答が明らかにするように、これはナンセンスです。インデックスは範囲の列挙であり、2つのことを並行して列挙できない理由はありません...これがまさにEnumerable.Selectのインデックス形式が行うことです。
ジムバルター2013年

11
基本的なコード例:for(var i = 0; i < myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}
Chad Hedgcock 2015

22
@JimBalter:私が読んだ最初のリンクです。それがあなたの立場をどのようにサポートしているかはわかりません。また、返信時にアドホムや意見の分かれる言葉を投げかけません。例として、「ナンセンス」、「並外れた混乱」、「完全に間違って混乱」、「37の賛成票を得た」などを引用します(ここでは、あなたのエゴは少しずれていると思います)。代わりに、 「argumentum ad verecundiam」を使って他の人に迷惑をかけないように丁寧にお願いしたいと思います。代わりに、StackOverflowで人々をサポートする方法を考えることをお勧めします。そういう意味では、ジョン・スキートはモデル市民だと思います。
プレッツェル

11
プレッツェル:私が経験したように、彼がモデル市民としてジョンスキートを使用するのは間違っています。ジム・バルター:あなたのコメントの一部は過度に攻撃的であり、あなたは怒りを少なくし、より多くの教育であなたのポイントを提示することができたでしょう。
Suncat2000 2016

7
@Pretzel Jimのポイントは、要素を整数のシーケンスにマップできる限り、インデックスを付けることができるということです。クラス自体がインデックスを格納しないことは重要な点です。また、リンクリストがあることない順序を持っている唯一のジムの地位を強化。必要なのは、各要素に順番に番号を付けることだけです。具体的には、反復中にカウントインクリメントするか、同じ長さの整数のリストを生成てから圧縮することができます(Pythonのzip関数の場合と同様)。
jpmc26 2017

666

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

関数の2番目のパラメーター[Select内]は、ソース要素のインデックスを表します。

new { i, value }新しい作成された匿名オブジェクトを

ValueTupleC#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>

9
このソリューションは、テンプレートのきちんとしたデザインが重要な設計上の問題であり、列挙されているすべてのアイテムのインデックスを使用したいRazorテンプレートの場合に適しています。ただし、「ラッピング」からのオブジェクトの割り当ては、整数の(避けられない)増分に加えて、(空間と時間の)費用を追加することに注意してください。
David Bullock 2014

6
@mjsrドキュメントはこちらです。
Thorkil Holm-Jacobsen

19
申し訳ありませんが、それは賢い方法ですが、foreachの外側にインデックスを作成して各ループをインクリメントするよりも読みやすいでしょうか。
jbyrd 2017年

13
新しいC#バージョンではタプルも使用するため、次のようなものになります:foreach(var(item、i)in Model.Select((v、i)=>(v、i)))タプル分解を使用してforループ内でアイテムとインデックス(i)に直接アクセスします。
Haukman、2018年

5
これが良い答えである理由を誰かに説明してもらえますか(執筆時点では450票以上)。私が見る限り、単純にカウンタをインクリメントするよりも理解するのが難しいため、保守性が低く、メモリを多く使用し、おそらく遅くなります。何か不足していますか?
リッチN

182

最後に、C#7には、foreachループ(つまりタプル)内でインデックスを取得するための適切な構文があります。

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

8
この回答は過小評価されており、タプルを使用する方がずっとクリーンです
Todd

7
nullコレクションを処理するように変更:public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self?.Select((item, index) => (item, index)) ?? new List<(T, int)>();
2Toad '25 / 11/18

良いですね。私はこのソリューションが一番好きです。
FranzHuber23

これが最善の答えです
w0ns88

2
このメソッドを呼び出して、他の言語にEnumerated慣れている人にわかりやすくする(およびタプルパラメータの順序も入れ替える)と便利です。それはとにかく明白ではありません。WithIndex
FernAndr

114

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

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

12
それは「本当に」問題を解決しません。アイデアは良いですが、追加のカウント変数を避けていません
Atmocreations 09/12/31

forループ内にreturnステートメントがある場合、これは機能しません。そのために "ForEachWithIndex"を変更すると、汎用的ではなくなり、通常のforループを記述した方がよい
Shankar Raju

ForEachWithIndex呼び出しは、文字列とインデックスを取得するLinq Selectを使用した次の呼び出しと同等です。 values.Select((item, idx) => { Console.WriteLine("{0}: {1}", idx, item); return item; }).ToList();
user2023861

95

forほとんどの場合、ループの方が適しているというコメントには同意しません。

foreachは便利な構造でありfor、すべての状況でループに置き換えることはできません。

たとえば、DataReaderがあり、foreachそれを使用してすべてのレコードをループする場合、それは自動的にDisposeメソッドを呼び出し、リーダーを閉じます(これにより、接続を自動的に閉じることができます)。そのため、リーダーを閉じるのを忘れた場合でも、接続のリークが防止されるため、これはより安全です。

(確かに常にリーダーを閉じることは良い習慣ですが、そうしないとコンパイラーはそれをキャッチしません-すべてのリーダーを閉じたことを保証することはできませんが、取得によって接続をリークしないようにすることができますforeachを使用する習慣がある。)

Disposeメソッドの暗黙的な呼び出しが役立つ他の例があるかもしれません。


2
これを指摘してくれてありがとう。むしろ微妙。詳細については、pvle.be / 2010/05 / foreach-statement-calls-dispose-on-ienumeratorおよびmsdn.microsoft.com/en-us/library/aa664754(VS.71).aspxを参照してください
Mark Meuer、2011年

+1。Programmers.SEとのforeach違いfor(およびこれに近いwhile)について、より詳細に書いていました。
Arseni Mourzenko 2013

64

文字通りの答え-警告、パフォーマンスは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);
}

3
Cast <T>()の代わりにOfType <T>()を使用する唯一の理由は、列挙内の一部の項目が明示的なキャストに失敗する可能性がある場合です。オブジェクトの場合、これは当てはまりません。
dahlbyk 2009年

13
もちろん、Castの代わりにOfTypeを使用する他の理由を除いて、Castを使用することはありません。
エイミーB

36

LINQ、C#7、およびSystem.ValueTupleNuGetパッケージを使用して、これを行うことができます。

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

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


これはuser1414213562の回答とどう違うのですか?
Edward Brey

@EdwardBreyそうではないと思います。この質問には多くの答えがありますが、私はおそらくそれを逃したか、それが何をしているかに気付かなかったのは、彼がいくつかのロジックを拡張メソッドに分離したためです。
Pavel

1
.SelectはLINQから組み込まれているため、これは異なります。独自の関数を記述する必要はありませんか?ただし、VSに「System.ValueTuple」をインストールする必要があります。
アントン

33

@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を返すため、反復する要素が不足すると、ループ条件によってループが停止します。


10
listEnumerator.MoveNext()== trueを比較する必要はありません。これは、true == trueかどうかをコンピューターに尋ねるようなものですか?:) if if listEnumerator.MoveNext(){}
Zesty

9
@ゼスティ、あなたは絶対的に正しいです。この場合、特に条件としてi <blahSize以外のものを入力することに慣れていない人にとっては、それを追加する方が読みやすいと感じました。
Gezim 2013年

@Gezimここでは述語は気にしませんが、これは述語のように見えないという点であなたのポイントを理解します。
John Dvorak 2016年

1
列挙子を破棄する必要があります。
アントニン・Lejsek

@AntonínLejsek列挙子は実装されていませんIDisposable
Edward Brey

27

カウンター変数の使用には何の問題もありません。実際には、あなたが使用するかどうかforforeach whileあるいはdo、カウンタ変数は、どこかで宣言され、インクリメントされなければなりません。

したがって、適切にインデックスが付けられたコレクションがあるかどうかわからない場合は、このイディオムを使用してください。

var i = 0;
foreach (var e in collection) {
   // Do stuff with 'e' and 'i'
   i++;
}

または、インデックス可能なコレクションがインデックスアクセス用のO(1)であることがわかっている場合は、これを使用します(これはおそらく(ドキュメントには記載されていません)、他のタイプ(など)では必ずしも必要ではありません)。ArrayList<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年なので、上記を使用します。これらの複数のコアをうまく​​活用して、処理を高速化したいと考えています。さらに、 'sequential' LINQの場合、ForEach()拡張メソッドを取得できるのはList<T>andArray ...だけでありforeach、醜い構文のためにシングルスレッドを実行しているため、それを使用することがsimpleを実行するよりも優れていることは明らかではありません。


26

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

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

これは実際にはアイテムのインデックスを返しません。代わりに、リストのサブリストのみの可能性がある列挙リスト内のインデックスを返します。これにより、サブリストとリストのサイズが等しい場合にのみ正確なデータが得られます。基本的に、コレクションにリクエストされたタイプではないオブジェクトが含まれている場合、インデックスは正しくありません。
Lucas B

7
@Lucas:いいえ、ただし、現在のforeach反復のインデックスを返します。それが問題でした。
ブライアンギデオン

20

これが私がこの問題に対して思いついた解決策です

元のコード:

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

16

独自のインデックスを追加するだけです。複雑にしないでおく。

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

15

IEnumerableではなく、Listでのみ機能しますが、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のどこにいるのかを数えておき、より単純で、より適応性のあるものにした方がよいでしょう。


5
重い投票についてはわかりません。確かにパフォーマンスはこれを禁止しますが、あなたは質問に答えました!
マットミッチェル

4
これに関するもう1つの問題は、リスト内の項目が一意である場合にのみ機能することです。
CodesInChaos、2011

14

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

はい、MSはCLR / BCLを拡張して、この種のものをネイティブにする必要があります。
トッド、

14

なぜ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)

8
indexOfソリューションは、重複のあるリストには無効であり、非常に低速です。
ティムタム

2
回避すべき問題は、IEnumerableを複数回トラバースする場合です。たとえば、アイテムの数を取得してから、各アイテムを取得します。これは、たとえばIEnumerableがデータベースクエリの結果である場合に影響します。
デビッドクラーク

2
myList.IndexOf()はO(n)なので、ループはO(n ^ 2)になります。
Patrick Beard

9
int index;
foreach (Object o in collection)
{
    index = collection.indexOf(o);
}

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


68
2つの問題:1)これはO(n^2)、ほとんどの実装でであるためIndexOfですO(n)。2)リストに重複するアイテムがある場合、これは失敗します。
CodesInChaos、2011

15
注:O(n ^ 2)は、大きなコレクションの場合、これが非常に遅くなる可能性があることを意味します。
O'Rooney

IndexOfメソッドを使用する優れた発明!これは、foreachループでインデックス(番号)を取得するために探していたものです。Big Thx
Mitja Bonca 2013

19
神様、私はあなたがそれを使わなかったことを望みます!:(作成したくない変数を使用します-実際には、n + 1整数を作成します。これは、その関数も返すために1を作成する必要があるためです。-そして、indexof検索は、各ステップで1つの整数インクリメント演算。なぜ人々はこの回答に反対票を投じないのですか?
canahari

13
この答えは使わないでください。コメントの1つで述べられている難しい真実を見つけました。"リストに重複するアイテムがある場合、これは失敗します。" !!!
ブルース

9

これは私がそれを行う方法であり、その単純さ/簡潔さのために素晴らしいですが、ループ本体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);
    ...
}

8

この回答:言語の直接サポートについてC#言語チームに働きかけます。

主な答えは次のとおりです。

明らかに、インデックスの概念は列挙の概念とは無関係であり、実行することはできません。

これは現在のC#言語バージョン(2020)に当てはまりますが、これは概念的なCLR /言語の制限ではなく、実行できます。

Microsoft C#言語開発チームは、新しいインターフェイスIIndexedEnumerableのサポートを追加することにより、新しいC#言語機能を作成できます

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 ()使用され、with var index存在する場合、コンパイラは、宣言にアイテムのコレクションを期待IIndexedEnumerableインターフェース。インターフェイスが存在しない場合、コンパイラはソースをIndexedEnumerableオブジェクトでポリフィルラップできます。これにより、インデックスを追跡するためのコードが追加されます。

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

後で、CLRを更新して内部インデックストラッキングを設定できます。これは、withキーワードが指定され、ソースが直接実装されていない場合にのみ使用されますIIndexedEnumerable

なぜ:

  • Foreachの方が見栄えがよく、ビジネスアプリケーションでは、foreachループがパフォーマンスのボトルネックになることはめったにありません
  • Foreachはメモリ上でより効率的です。各ステップで新しいコレクションに変換する代わりに、関数のパイプラインを用意します。CPUキャッシュフォールトとガベージコレクションの数が少ないときに、CPUサイクルをさらに2、3使用するかどうかは誰が気にしますか?
  • インデックス追跡コードを追加するためにコーダーを要求すると、美しさが台無しになります
  • 実装は非常に簡単(Microsoftにお願いします)で、下位互換性があります

ここでは、ほとんどの人が、マイクロソフトの従業員ではありませんが、これは正しい答え、あなたは、このような機能を追加するためにMicrosoftに働きかけることができます。拡張関数を使用して独自のイテレータを作成し、タプルを使用することもできますが、Microsoftは拡張関数を回避するために構文糖をまき散らすことができます


待ってください、それでこの言語機能はすでに存在していますか、それとも将来のために提案されていますか?
Pavel

1
@Pavel答えを明確にするために更新しました。この回答は、「明らかに、インデックスの概念は列挙の概念とは無関係であり、実行できない」という主要な回答に対抗するために提供されました。
Todd

6

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

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

13
そして今、アルゴリズムはO(n ^ 2)です(悪くない場合)。これを使用する前私は非常に慎重に考えます。@crucibleの回答の複製
BradleyDotNET、2015年

1
@BradleyDotNETはまさに正しいです。このバージョンを使用しないでください。
オスカー

1
これに注意してください!リストに重複したアイテムがある場合、最初のアイテムの位置を取得します!
Sonhja

5

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

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

5

ループは次のように書くことができます:

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

3

この問題の私の解決策は、拡張メソッドです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));

struct(index、item)のペアにはa を使用します。
CodesInChaos

3

興味深いことに、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構文を確実に置き換えることはできません。 。


3

これはかなり効率的ではないと思いますが、うまくいきます:

@foreach (var banner in Model.MainBanners) {
    @Model.MainBanners.IndexOf(banner)
}

3

私はこれを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);

c。でstring.joinを使用することもできます。例:var joinResult = string.Join( "、"、listOfNames); '
ウォーレンラフランス

1
誰かがこのO(n * n)のことを私に説明できますか?
アクセル

@Axelそれは基本的に存在する場合、すなわち、操作は二次結果の増加を計算するために必要なことを意味n項目は、その後の操作がされているn * n、またはn-squared。en.wikipedia.org/wiki/…–
Richard Hansell

2

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

なぜあなたは知りたいのですか?

ほとんどのリクリーは次の3つのことの1つを行っているようです。

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

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

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


4)私が数回ヒットした場合は、最初または最後のパスで実行する必要がある別のケースです-たとえば、印刷するオブジェクトのリストで、アイテム間にコンマが必要だが、最後のアイテムの後には必要ない場合などです。
Loren Pechtel 2010

2

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

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


2

このようなものはどうですか?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;
}

ここに複数の問題があります。A)余分なブレース。B)文字列連結+ =ループ反復ごと。
enorl76 2013

2

私はこの問題を抱えていましたが、私の場合の問題を考えると、期待される解決策とは無関係に最良の解決策が得られました。

よくあるケースかもしれませんが、基本的には、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]});
  }
}

常に適用できるわけではありませんが、言及する価値があるほど頻繁にあると思います。

とにかく、あなたが持っているロジックにすでに自明ではない解決策があることは重要です...


2

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

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