Wie erhält man den Index der aktuellen Iteration einer foreach-Schleife?

939

Gibt es in C # ein seltenes Sprachkonstrukt, auf das ich nicht gestoßen bin (wie die wenigen, die ich kürzlich gelernt habe, einige über Stapelüberlauf), um einen Wert zu erhalten, der die aktuelle Iteration einer foreach-Schleife darstellt?

Zum Beispiel mache ich derzeit je nach den Umständen so etwas:

int i = 0;
foreach (Object o in collection)
{
    // ...
    i++;
}
Matt Mitchell
quelle
1
Das Abrufen von Castings ist für mich im Allgemeinen nicht optimierter als nur die Verwendung des indexbasierten Zugriffs auf eine Sammlung, obwohl dies in vielen Fällen gleich ist. Der Zweck von foreach besteht darin, Ihren Code lesbar zu machen, fügt jedoch (normalerweise) eine Indirektionsebene hinzu, die nicht kostenlos ist.
Brian
8
Ich würde sagen, der Hauptzweck von foreachbesteht darin, einen gemeinsamen Iterationsmechanismus für alle Sammlungen bereitzustellen, unabhängig davon, ob sie indexierbar sind ( List) oder nicht ( Dictionary).
Brian Gideon
2
Hallo Brian Gideon - stimme definitiv zu (dies war vor ein paar Jahren und ich war zu der Zeit weit weniger erfahren). Während jedoch Dictionarynicht indexierbar ist, eine Iteration von Dictionarynicht durchquert sie in einer bestimmten Reihenfolge (dh ein Enumerator ist indexierbar durch die Tatsache ergibt es Elemente sequentiell). In diesem Sinne könnten wir sagen, dass wir nicht nach dem Index innerhalb der Auflistung suchen, sondern nach dem Index des aktuellen Aufzählungselements innerhalb der Aufzählung (dh ob wir uns beim ersten oder fünften oder letzten Aufzählungselement befinden).
Matt Mitchell
4
Mit foreach kann der Compiler auch Grenzen überspringen, indem er jeden Arrayzugriff im kompilierten Code überprüft. Wenn Sie for mit einem Index verwenden, überprüft die Laufzeit, ob Ihr Indexzugriff sicher ist.
IvoTops
1
Aber es ist falsch. Wenn Sie die Iterationsvariable einer for-Schleife innerhalb der Schleife nicht ändern, kennt der Compiler die Grenzen und muss sie nicht erneut überprüfen. Dies ist ein so häufiger Fall, dass jeder anständige Compiler ihn implementieren wird.
Jim Balter

Antworten:

552

Das foreachdient zum Durchlaufen von implementierten Sammlungen IEnumerable. Dies geschieht durch Aufrufen GetEnumeratorder Sammlung, die eine zurückgibt Enumerator.

Dieser Enumerator hat eine Methode und eine Eigenschaft:

  • MoveNext ()
  • Aktuell

CurrentGibt das Objekt zurück, auf dem sich Enumerator gerade befindet, und wird auf das nächste Objekt MoveNextaktualisiert Current.

Das Konzept eines Index ist dem Konzept der Aufzählung fremd und kann nicht durchgeführt werden.

Aus diesem Grund können die meisten Sammlungen mit einem Indexer und dem for-Schleifenkonstrukt durchlaufen werden.

Ich bevorzuge in dieser Situation die Verwendung einer for-Schleife im Vergleich zur Verfolgung des Index mit einer lokalen Variablen.

FlySwat
quelle
165
"Offensichtlich ist das Konzept eines Index dem Konzept der Aufzählung fremd und kann nicht durchgeführt werden." - Das ist Unsinn, wie die Antworten von David B und bcahill deutlich machen. Ein Index ist eine Aufzählung über einen Bereich, und es gibt keinen Grund, warum man nicht zwei Dinge parallel aufzählen kann ... genau das tut die Indexierungsform von Enumerable.Select.
Jim Balter
11
Grundlegendes Codebeispiel:for(var i = 0; i < myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}
Chad Hedgcock
22
@ JimBalter: Das ist der erste Link, den ich gelesen habe. Ich sehe nicht, wie es Ihre Position unterstützt. Ich schleudere auch keine Ad-Homs und spaltenden Worte auf Leute, wenn ich antworte. Ich zitiere als Beispiel Ihre Verwendung von "Unsinn", "außerordentlicher Verwirrung", "völlig falsch und verwirrt", "Ich habe 37 positive Stimmen" usw. (Ich denke, Ihr Ego steht hier ein bisschen auf dem Spiel.) Stattdessen ich Ich möchte Sie höflich bitten, andere nicht mit Ihrem 'argumentum ad verecundiam' zu verprügeln, und ich möchte Sie stattdessen ermutigen, über Möglichkeiten nachzudenken, wie Sie die Leute hier auf StackOverflow unterstützen können. Ich würde sagen, Jon Skeet ist in dieser Hinsicht ein vorbildlicher Bürger.
Brezel
11
Brezel: Ihre Verwendung von Jon Skeet als vorbildlicher Bürger ist falsch, da er jemanden, der ihm nicht zustimmt, wie ich erfahren habe, mit der Stirn schlagen (und ablehnen) wird. Jim Balter: Einige Ihrer Kommentare waren zu aggressiv und Sie hätten Ihre Punkte mit weniger Ärger und mehr Bildung präsentieren können.
Suncat2000
7
@Pretzel Jims Punkt ist, dass Sie die Elemente indizieren können, solange Sie sie einer Folge von Ganzzahlen zuordnen können. Dass die Klasse selbst keinen Index speichert, ist nebensächlich. Darüber hinaus , dass die verknüpfte Liste hat einen Auftrag hat stärkt nur Jims Position. Alles was Sie tun müssen, ist jedes Element der Reihe nach zu nummerieren. Insbesondere können Sie dies erreichen, indem Sie eine Anzahl erhöhen, während Sie iterieren, oder Sie können eine Liste von Ganzzahlen mit derselben Länge generieren und diese dann komprimieren (wie in Pythons zipFunktion).
jpmc26
666

Ian Mercer hat eine ähnliche Lösung wie diese auf Phil Haacks Blog veröffentlicht :

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

Dadurch erhalten Sie das Element ( item.value) und seinen Index ( item.i), indem Sie diese Überladung von LINQs verwendenSelect :

Der zweite Parameter der Funktion [inside Select] repräsentiert den Index des Quellelements.

Das new { i, value }erstellt ein neues anonymes Objekt .

Heap-Zuweisungen können vermieden werden, indem ValueTupleSie C # 7.0 oder höher verwenden:

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

Sie können das auch item.durch automatische Destrukturierung beseitigen :

<ol>
foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i )))
{
    <li id="item_@i">@value</li>
}
</ol>
bcahill
quelle
9
Diese Lösung eignet sich für den Fall einer Razor-Vorlage, bei der die Sauberkeit der Vorlage ein nicht triviales Designproblem darstellt und Sie auch den Index jedes aufgezählten Elements verwenden möchten. Beachten Sie jedoch, dass die Objektzuweisung aus dem 'Wrapping' zusätzlich zum (unvermeidbaren) Inkrementieren einer Ganzzahl zusätzliche Kosten (in Bezug auf Raum und Zeit) verursacht.
David Bullock
6
@mjsr Die Dokumentation ist hier .
Thorkil Holm-Jacobsen
19
Entschuldigung - das ist klug, aber ist es wirklich besser lesbar, als einen Index außerhalb von foreach zu erstellen und ihn in jeder Schleife zu erhöhen?
17.
13
In den späteren C # -Versionen verwenden Sie also auch Tupel, sodass Sie Folgendes haben: foreach (var (item, i) in Model.Select ((v, i) => (v, i))) Ermöglicht dies Greifen Sie auf das Element und den Index (i) direkt in der for-Schleife mit Tupeldekonstruktion zu.
Haukman
5
Kann mir jemand erklären, warum dies eine gute Antwort ist (über 450 Upvotes zum Zeitpunkt des Schreibens)? Soweit ich sehen kann, ist es schwieriger zu verstehen als nur einen Zähler zu erhöhen, ist daher weniger wartbar, verbraucht mehr Speicher und ist wahrscheinlich langsamer. Vermisse ich etwas
Rich N
182

Schließlich hat C # 7 eine anständige Syntax, um einen Index innerhalb einer foreachSchleife (dh Tupel) zu erhalten:

foreach (var (item, index) in collection.WithIndex())
{
    Debug.WriteLine($"{index}: {item}");
}

Eine kleine Erweiterungsmethode wäre erforderlich:

public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)       
   => self.Select((item, index) => (item, index)); 
user1414213562
quelle
8
Diese Antwort wird unterschätzt, die Tupel zu haben ist viel sauberer
Todd
7
Geändert, um public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self?.Select((item, index) => (item, index)) ?? new List<(T, int)>();
Nullsammlungen
Schön. Diese Lösung gefällt mir am besten.
FranzHuber23
Dies ist die beste Antwort
w0ns88
2
Es kann nützlich sein, die Methode Enumeratedso aufzurufen , dass sie für Personen, die an andere Sprachen gewöhnt sind , besser erkennbar ist (und möglicherweise auch die Reihenfolge der Tupelparameter vertauscht). Nicht, dass WithIndexdas sowieso nicht offensichtlich wäre.
FernAndr
114

Könnte so etwas tun:

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));
    }
}
Brad Wilson
quelle
12
Das löst das Problem nicht "wirklich". Die Idee ist gut, aber sie vermeidet nicht die zusätzliche Zählvariable
Atmocreations
Dies funktioniert nicht, wenn wir eine return-Anweisung in unserer for-Schleife haben. Wenn Sie "ForEachWithIndex" dafür ändern, ist es nicht generisch, besser, eine reguläre for-Schleife zu schreiben
Shankar Raju
Ihr ForEachWithIndex-Aufruf entspricht diesem mit der Linq Select, die eine Zeichenfolge und einen Index verwendet:values.Select((item, idx) => { Console.WriteLine("{0}: {1}", idx, item); return item; }).ToList();
user2023861
95

Ich bin mit Kommentaren nicht einverstanden, dass eine forSchleife in den meisten Fällen die bessere Wahl ist.

foreachist ein nützliches Konstrukt und kann nicht unter forallen Umständen durch eine Schleife ersetzt werden.

Wenn Sie beispielsweise einen DataReader haben und alle Datensätze mit einem durchlaufen, foreachruft er automatisch die Dispose- Methode auf und schließt den Reader (der dann die Verbindung automatisch schließen kann). Dies ist daher sicherer, da es Verbindungslecks verhindert, selbst wenn Sie vergessen, das Lesegerät zu schließen.

(Sicher, es ist eine gute Praxis, Leser immer zu schließen, aber der Compiler wird es nicht abfangen, wenn Sie dies nicht tun. Sie können nicht garantieren, dass Sie alle Leser geschlossen haben, aber Sie können es wahrscheinlicher machen, dass Sie keine Verbindungen verlieren, indem Sie abrufen in der Gewohnheit, foreach zu verwenden.)

Es kann andere Beispiele dafür geben, dass der implizite Aufruf der DisposeMethode nützlich ist.

mike nelson
quelle
2
Vielen Dank für den Hinweis. Eher subtil. Weitere Informationen erhalten Sie unter pvle.be/2010/05/foreach-statement-calls-dispose-on-ienumerator und msdn.microsoft.com/en-us/library/aa664754(VS.71).aspx .
Mark Meuer
+1. Ich habe ausführlicher darüber geschrieben, wie foreachsich Programmers.SE von for(und näher an while) Programmers.SE unterscheidet .
Arseni Mourzenko
64

Wörtliche Antwort - Warnung: Die Leistung ist möglicherweise nicht so gut wie die Verwendung von a intzur Verfolgung des Index. Zumindest ist es besser als zu benutzen IndexOf.

Sie müssen nur die Indexüberladung von Select verwenden, um jedes Element in der Sammlung mit einem anonymen Objekt zu versehen, das den Index kennt. Dies kann gegen alles erfolgen, was IEnumerable implementiert.

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);
}
Amy B.
quelle
3
Der einzige Grund, OfType <T> () anstelle von Cast <T> () zu verwenden, besteht darin, dass einige Elemente in der Aufzählung eine explizite Umwandlung möglicherweise nicht bestehen. Für Objekte wird dies niemals der Fall sein.
Dahlbyk
13
Sicher, bis auf den anderen Grund, OfType anstelle von Cast zu verwenden - das heißt, ich verwende Cast nie.
Amy B
36

Mit LINQ, C # 7 und dem System.ValueTupleNuGet-Paket können Sie Folgendes tun:

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

Sie können das reguläre foreachKonstrukt verwenden und direkt auf den Wert und den Index zugreifen, nicht als Mitglied eines Objekts. Beide Felder bleiben nur im Bereich der Schleife. Aus diesen Gründen halte ich dies für die beste Lösung, wenn Sie C # 7 und verwenden können System.ValueTuple.

Pavel
quelle
Wie unterscheidet sich das von der Antwort von user1414213562 ?
Edward Brey
@ EdwardBrey Ich nehme an, es ist nicht. Diese Frage hat viele Antworten, ich habe sie wahrscheinlich nur verpasst oder nicht bemerkt, was sie tat, weil er einen Teil der Logik in eine Erweiterungsmethode aufgeteilt hat.
Pavel
1
Dies ist anders, da .Select in LINQ integriert ist. Sie müssen keine eigene Funktion schreiben? VS muss jedoch "System.ValueTuple" installiert haben.
Anton
33

Mit der Antwort von @ FlySwat habe ich folgende Lösung gefunden:

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

Sie erhalten den Enumerator mit GetEnumeratorund dann eine Schleife mit einer forSchleife. Der Trick besteht jedoch darin, den Zustand der Schleife herzustellen listEnumerator.MoveNext() == true.

Da die MoveNextMethode eines Enumerators true zurückgibt, wenn es ein nächstes Element gibt und auf das zugegriffen werden kann, stoppt die Schleifenbedingung die Schleife, wenn uns die Elemente zum Durchlaufen ausgehen.

Gezim
quelle
10
ListEnumerator.MoveNext () == true muss nicht verglichen werden. Das ist so, als würde man den Computer fragen, ob true == true? :) Sagen Sie einfach, wenn listEnumerator.MoveNext () {}
Bitte klicken Sie hier
9
@Zesty, du bist absolut richtig. Ich fand, dass es in diesem Fall besser lesbar ist, es hinzuzufügen, insbesondere für Leute, die es nicht gewohnt sind, etwas anderes als i <blahSize als Bedingung einzugeben.
Gezim
@Gezim Ich habe nichts gegen Prädikate hier, aber ich verstehe, dass dies nicht wie ein Prädikat aussieht.
John Dvorak
1
Sie sollten den Enumerator entsorgen.
Antonín Lejsek
@ AntonínLejsek Der Enumerator wird nicht implementiert IDisposable.
Edward Brey
27

Es ist nichts Falsches daran, eine Zählervariable zu verwenden. In der Tat, ob Sie verwenden for, foreach whileoder domuss eine Zählvariable irgendwo deklariert und erhöht werden.

Verwenden Sie diese Redewendung, wenn Sie nicht sicher sind, ob Sie eine entsprechend indizierte Sammlung haben:

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

Verwenden Sie diese Option andernfalls, wenn Sie wissen, dass Ihre indizierbare Sammlung O (1) für den Indexzugriff ist (für die sie bestimmt ist Arrayund wahrscheinlich auch List<T>(in der Dokumentation nicht angegeben), jedoch nicht unbedingt für andere Typen (z. B. 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'
}

Es sollte niemals notwendig sein, das manuell IEnumeratordurch Aufrufen MoveNext()und Abfragen zu bedienen Current- foreachdas erspart Ihnen diese besondere Mühe ... Wenn Sie Elemente überspringen müssen, verwenden Sie einfach ein continueim Hauptteil der Schleife.

Und nur der Vollständigkeit halber, je nachdem , was Sie tun mit Ihrem Index (die obigen Konstrukte bieten viel Flexibilität), könnten Sie Parallel LINQ verwenden:

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

Wir verwenden AsParallel()oben, weil es bereits 2014 ist, und wir möchten diese mehreren Kerne gut nutzen, um die Dinge zu beschleunigen. Außerdem erhalten Sie für 'sequentielles' LINQ nur eine ForEach()Erweiterungsmethode für List<T>undArray ... und es ist nicht klar, dass die Verwendung besser ist als eine einfache foreach, da Sie immer noch Single-Threaded für eine hässlichere Syntax ausführen.

David Bullock
quelle
26

Sie können den ursprünglichen Enumerator mit einem anderen umschließen, der die Indexinformationen enthält.

foreach (var item in ForEachHelper.WithIndex(collection))
{
    Console.Write("Index=" + item.Index);
    Console.Write(";Value= " + item.Value);
    Console.Write(";IsLast=" + item.IsLast);
    Console.WriteLine();
}

Hier ist der Code für die ForEachHelperKlasse.

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;
        }            
    }
}
Brian Gideon
quelle
Dadurch wird der Index des Elements nicht zurückgegeben. Stattdessen wird der Index innerhalb der aufgezählten Liste zurückgegeben, die möglicherweise nur eine Unterliste der Liste ist, sodass Sie nur dann genaue Daten erhalten, wenn die Unterliste und die Liste gleich groß sind. Grundsätzlich ist Ihr Index jedes Mal falsch, wenn die Sammlung Objekte enthält, die nicht den angeforderten Typ haben.
Lucas B
7
@Lucas: Nein, aber es wird der Index der aktuellen foreach-Iteration zurückgegeben. Das war die Frage.
Brian Gideon
20

Hier ist eine Lösung, die ich gerade für dieses Problem gefunden habe

Originalcode:

int index=0;
foreach (var item in enumerable)
{
    blah(item, index); // some code that depends on the index
    index++;
}

Code aktualisiert

enumerable.ForEach((item, index) => blah(item, index));

Verlängerungsmethode:

    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;
    }
mat3
quelle
16

Fügen Sie einfach Ihren eigenen Index hinzu. Halte es einfach.

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

Es wird nur für eine Liste funktionieren und nicht für eine IEnumerable, aber in LINQ gibt es Folgendes:

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 Ich habe nicht gesagt, dass es eine großartige Antwort ist, ich habe nur gesagt, dass es nur zeigt, dass es möglich ist, das zu tun, was er gefragt hat :)

@Graphain Ich würde nicht erwarten, dass es schnell geht - ich bin mir nicht ganz sicher, wie es funktioniert. Es könnte jedes Mal die gesamte Liste durchgehen, um ein passendes Objekt zu finden, was eine Menge Vergleiche wäre.

Das heißt, List könnte einen Index jedes Objekts zusammen mit der Anzahl behalten.

Jonathan scheint eine bessere Idee zu haben, wenn er näher darauf eingehen würde?

Es wäre jedoch besser, nur zu zählen, wo Sie sich gerade befinden, einfacher und anpassungsfähiger.

Tiegel
quelle
5
Ich bin mir nicht sicher über die heftigen Abstimmungen. Sicher macht Leistung dies unerschwinglich, aber Sie haben die Frage beantwortet!
Matt Mitchell
4
Ein weiteres Problem dabei ist, dass es nur funktioniert, wenn die Elemente in der Liste eindeutig sind.
CodesInChaos
14

C # 7 gibt uns endlich eine elegante Möglichkeit, dies zu tun:

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}");
        }
    }
}
Paul Mitchell
quelle
Ja, und MS sollte die CLR / BCL erweitern, um solche Dinge nativ zu machen.
Todd
14

Warum foreach ?!

Der einfachste Weg ist die Verwendung von for anstelle von foreach, wenn Sie List verwenden :

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

Oder wenn Sie foreach verwenden möchten:

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

Sie können dies verwenden, um den Index jeder Schleife zu kennen:

myList.indexOf(m)
Parsa
quelle
8
Die indexOf-Lösung ist für Listen mit Duplikaten ungültig und auch sehr langsam.
Tymtam
2
Das zu vermeidende Problem besteht darin, dass Sie die IEnumerable mehrmals durchlaufen, z. B. um die Anzahl der Elemente und dann jedes Element zu ermitteln. Dies hat Auswirkungen, wenn IEnumerable beispielsweise das Ergebnis einer Datenbankabfrage ist.
David Clarke
2
myList.IndexOf () ist O (n), Ihre Schleife ist also O (n ^ 2).
Patrick Beard
9
int index;
foreach (Object o in collection)
{
    index = collection.indexOf(o);
}

Dies würde für unterstützende Sammlungen funktionieren IList.

Sachin
quelle
68
Zwei Probleme: 1) Dies ist O(n^2)da in den meisten Implementierungen IndexOfist O(n). 2) Dies schlägt fehl, wenn die Liste doppelte Elemente enthält.
CodesInChaos
15
Hinweis: O (n ^ 2) bedeutet, dass dies für eine große Sammlung katastrophal langsam sein kann.
O'Rooney
Tolle Erfindung zur Verwendung der IndexOf-Methode! Dies ist, wonach ich gesucht habe, um den Index (die Nummer) in jeder Schleife zu erhalten! Big Thx
Mitja Bonca
19
Gott, ich hoffe du hast das nicht benutzt! :( Es wird die Variable verwendet, die Sie nicht erstellen wollten - tatsächlich werden n + 1 Ints erstellt, da diese Funktion eine erstellen muss, um ebenfalls zurückzukehren - und dieser Suchindex ist viel, viel langsamer als eine ganzzahlige Inkrementoperation in jedem Schritt. Warum werden die Leute diese Antwort nicht
ablehnen
13
Verwenden Sie diese Antwort nicht, ich habe die harte Wahrheit gefunden, die in einem der Kommentare erwähnt wurde. "Dies schlägt fehl, wenn die Liste doppelte Elemente enthält." !!!
Bruce
9

So mache ich es, was für seine Einfachheit / Kürze gut ist, aber wenn Sie viel im Loop-Body tun obj.Value, wird es ziemlich schnell alt.

foreach(var obj in collection.Select((item, index) => new { Index = index, Value = item }) {
    string foo = string.Format("Something[{0}] = {1}", obj.Index, obj.Value);
    ...
}
Ian Henry
quelle
8

Diese Antwort: Setzen Sie sich beim C # -Sprachteam für direkte Sprachunterstützung ein.

Die führende Antwort lautet:

Offensichtlich ist das Konzept eines Index dem Konzept der Aufzählung fremd und kann nicht durchgeführt werden.

Dies gilt zwar für die aktuelle C # -Sprachenversion (2020), dies ist jedoch keine konzeptionelle CLR / Sprachbeschränkung, sondern kann durchgeführt werden.

Das Microsoft C # -Sprachenentwicklungsteam könnte eine neue C # -Sprachenfunktion erstellen, indem es die Unterstützung für eine neue Schnittstelle IIndexedEnumerable hinzufügt

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

Wenn foreach ()verwendet wird und with var indexvorhanden ist, erwartet der Compiler, dass die Elementauflistung die IIndexedEnumerableSchnittstelle deklariert . Wenn die Schnittstelle nicht vorhanden ist, kann der Compiler die Quelle mit einem IndexedEnumerable-Objekt umschließen, das den Code zum Verfolgen des Index hinzufügt.

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

Später kann die CLR aktualisiert werden, um eine interne Indexverfolgung zu erhalten, die nur verwendet wird, wenn ein withSchlüsselwort angegeben ist und die Quelle nicht direkt implementiert wirdIIndexedEnumerable

Warum:

  • Foreach sieht besser aus, und in Geschäftsanwendungen sind Foreach-Schleifen selten ein Leistungsengpass
  • Foreach kann im Speicher effizienter sein. Eine Pipeline von Funktionen haben, anstatt bei jedem Schritt in neue Sammlungen zu konvertieren. Wen interessiert es, wenn ein paar CPU-Zyklen mehr verwendet werden, wenn weniger CPU-Cache-Fehler und weniger Speicherbereinigungen vorliegen?
  • Wenn der Codierer Index-Tracking-Code hinzufügen muss, wird die Schönheit beeinträchtigt
  • Es ist recht einfach zu implementieren (bitte Microsoft) und abwärtskompatibel

Während die meisten Leute hier keine Microsoft-Mitarbeiter sind, ist dies eine richtige Antwort. Sie können sich bei Microsoft dafür einsetzen, eine solche Funktion hinzuzufügen. Sie könnten bereits einen eigenen Iterator mit einer Erweiterungsfunktion erstellen und Tupel verwenden , aber Microsoft könnte den syntaktischen Zucker streuen, um die Erweiterungsfunktion zu vermeiden

Todd
quelle
Warten Sie, gibt es diese Sprachfunktion bereits oder wird sie für die Zukunft vorgeschlagen?
Pavel
1
@ Pavel Ich habe die Antwort aktualisiert, um klar zu sein. Diese Antwort wurde bereitgestellt, um der führenden Antwort entgegenzuwirken, die besagt: "Offensichtlich ist das Konzept eines Index dem Konzept der Aufzählung fremd und kann nicht durchgeführt werden."
Todd
6

Wenn es sich bei der Sammlung um eine Liste handelt, können Sie List.IndexOf wie folgt verwenden:

foreach (Object o in collection)
{
    // ...
    @collection.IndexOf(o)
}
ssaeed
quelle
13
Und jetzt ist der Algorithmus O (n ^ 2) (wenn nicht schlechter). Ich würde sehr sorgfältig überlegen, bevor ich dies benutze. Es ist auch ein Duplikat von @crucibles Antwort
BradleyDotNET
1
@BradleyDotNET ist genau richtig, verwenden Sie diese Version nicht.
Oskar
1
Sei vorsichtig damit! Wenn Sie ein doppeltes Element in Ihrer Liste haben, erhält es die Position des ersten!
Sonhja
5

Verwenden Sie besser eine schlüsselwortsichere continueKonstruktion wie diese

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

Sie können Ihre Schleife folgendermaßen schreiben:

var s = "ABCDEFG";
foreach (var item in s.GetEnumeratorWithIndex())
{
    System.Console.WriteLine("Character: {0}, Position: {1}", item.Value, item.Index);
}

Nach dem Hinzufügen der folgenden Struktur und Erweiterungsmethode.

Die Struktur- und Erweiterungsmethode kapselt die Enumerable.Select-Funktionalität.

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);
    }
}
Zufrieden
quelle
3

Meine Lösung für dieses Problem ist eine Erweiterungsmethode WithIndex().

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

Verwenden Sie es wie

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));
ulrichb
quelle
Ich würde ein structfür das Paar (Index, Element) verwenden.
CodesInChaos
3

Aus Interesse hat Phil Haack gerade ein Beispiel dafür im Zusammenhang mit einem Delegierten mit Rasiermesser-Vorlage geschrieben ( http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx ).

Tatsächlich schreibt er eine Erweiterungsmethode, die die Iteration in eine "IteratedItem" -Klasse (siehe unten) einschließt, die den Zugriff auf den Index sowie das Element während der Iteration ermöglicht.

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

Obwohl dies in einer Nicht-Razor-Umgebung in Ordnung wäre, wenn Sie eine einzelne Operation ausführen (dh eine, die als Lambda bereitgestellt werden könnte), ist dies kein solider Ersatz für die for / foreach-Syntax in Nicht-Razor-Kontexten .

Matt Mitchell
quelle
3

Ich denke nicht, dass dies ziemlich effizient sein sollte, aber es funktioniert:

@foreach (var banner in Model.MainBanners) {
    @Model.MainBanners.IndexOf(banner)
}
Bart Calixto
quelle
3

Ich habe dies in LINQPad erstellt :

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.

Sie könnten auch einfach verwenden string.join:

var joinResult = string.Join(",", listOfNames);
Warren LaFrance
quelle
Sie können die Datei string.join auch in c # verwenden. Beispiel: var joinResult = string.Join (",", listOfNames); '
Warren LaFrance
1
Kann mir jemand erklären, was dieses O (n * n) Ding ist?
Axel
@Axel bedeutet im Grunde, dass die Operationen, die zur Berechnung des Ergebnisses erforderlich sind, quadratisch zunehmen, dh wenn es nElemente gibt, sind die Operationen n * noder n-quadratisch. en.wikipedia.org/wiki/…
Richard Hansell
2

Ich glaube nicht, dass es eine Möglichkeit gibt, den Wert der aktuellen Iteration einer foreach-Schleife zu ermitteln. Sich selbst zu zählen, scheint der beste Weg zu sein.

Darf ich fragen, warum Sie es wissen wollen?

Es scheint, dass Sie höchstwahrscheinlich eines von drei Dingen tun würden:

1) Das Objekt aus der Sammlung abrufen, aber in diesem Fall haben Sie es bereits.

2) Zählen der Objekte für die spätere Nachbearbeitung ... Die Sammlungen verfügen über eine Count-Eigenschaft, die Sie verwenden können.

3) Festlegen einer Eigenschaft für das Objekt basierend auf seiner Reihenfolge in der Schleife ... obwohl Sie dies leicht festlegen können, wenn Sie das Objekt zur Sammlung hinzufügen.

bryansh
quelle
4) Der Fall, den ich mehrmals getroffen habe, ist etwas anderes, das beim ersten oder letzten Durchgang ausgeführt werden muss - sagen Sie eine Liste der Objekte, die Sie drucken möchten, und Sie benötigen Kommas zwischen Elementen, jedoch nicht nach dem letzten Element.
Loren Pechtel
2

Wenn Ihre Sammlung den Index des Objekts nicht über eine Methode zurückgeben kann, besteht die einzige Möglichkeit darin, einen Zähler wie in Ihrem Beispiel zu verwenden.

Wenn Sie jedoch mit Indizes arbeiten, besteht die einzig vernünftige Antwort auf das Problem darin, eine for-Schleife zu verwenden. Alles andere führt zu einer Komplexität des Codes, ganz zu schweigen von der Komplexität von Zeit und Raum.

Joseph Daigle
quelle
2

Wie wäre es mit so etwas? Beachten Sie, dass myDelimitedString möglicherweise null ist, wenn myEnumerable leer ist.

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;
}
Matt Towers
quelle
Mehrere Probleme hier. A) zusätzliche Klammer. B) string concat + = pro Schleifeniteration.
Enorl76
2

Ich hatte gerade dieses Problem, aber das Nachdenken über das Problem in meinem Fall ergab die beste Lösung, unabhängig von der erwarteten Lösung.

Es kann durchaus vorkommen, dass ich aus einer Quellliste lese und darauf basierende Objekte in einer Zielliste erstelle. Ich muss jedoch zuerst prüfen, ob die Quellelemente gültig sind, und die Zeile eines beliebigen Objekts zurückgeben Error. Auf den ersten Blick möchte ich den Index in den Enumerator des Objekts in der Current-Eigenschaft einfügen. Beim Kopieren dieser Elemente kenne ich den aktuellen Index jedoch implizit vom aktuellen Ziel. Natürlich hängt es von Ihrem Zielobjekt ab, aber für mich war es eine Liste, und höchstwahrscheinlich wird es ICollection implementieren.

dh

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

Nicht immer zutreffend, aber oft genug, um erwähnenswert zu sein, denke ich.

Der Punkt ist jedenfalls, dass es manchmal bereits eine nicht offensichtliche Lösung in der Logik gibt, die Sie haben ...

nicodemus13
quelle
2

Ich war mir nicht sicher, was Sie mit den Indexinformationen basierend auf der Frage versuchen wollten. In C # können Sie jedoch normalerweise die IEnumerable.Select-Methode anpassen, um den Index aus dem gewünschten Wert herauszuholen. Zum Beispiel könnte ich so etwas verwenden, um festzustellen, ob ein Wert ungerade oder gerade ist.

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

Dies würde Ihnen ein Wörterbuch mit Namen geben, ob das Element in der Liste ungerade (1) oder gerade (0) war.

Kasey Speakman
quelle