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++;
}
foreach
besteht darin, einen gemeinsamen Iterationsmechanismus für alle Sammlungen bereitzustellen, unabhängig davon, ob sie indexierbar sind (List
) oder nicht (Dictionary
).Dictionary
nicht indexierbar ist, eine Iteration vonDictionary
nicht 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).Antworten:
Das
foreach
dient zum Durchlaufen von implementierten SammlungenIEnumerable
. Dies geschieht durch AufrufenGetEnumerator
der Sammlung, die eine zurückgibtEnumerator
.Dieser Enumerator hat eine Methode und eine Eigenschaft:
Current
Gibt das Objekt zurück, auf dem sich Enumerator gerade befindet, und wird auf das nächste ObjektMoveNext
aktualisiertCurrent
.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.
quelle
for(var i = 0; i < myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}
zip
Funktion).Ian Mercer hat eine ähnliche Lösung wie diese auf Phil Haacks Blog veröffentlicht :
Dadurch erhalten Sie das Element (
item.value
) und seinen Index (item.i
), indem Sie diese Überladung von LINQs verwendenSelect
:Das
new { i, value }
erstellt ein neues anonymes Objekt .Heap-Zuweisungen können vermieden werden, indem
ValueTuple
Sie C # 7.0 oder höher verwenden:Sie können das auch
item.
durch automatische Destrukturierung beseitigen :quelle
Schließlich hat C # 7 eine anständige Syntax, um einen Index innerhalb einer
foreach
Schleife (dh Tupel) zu erhalten:Eine kleine Erweiterungsmethode wäre erforderlich:
quelle
public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self?.Select((item, index) => (item, index)) ?? new List<(T, int)>();
Enumerated
so 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, dassWithIndex
das sowieso nicht offensichtlich wäre.Könnte so etwas tun:
quelle
values.Select((item, idx) => { Console.WriteLine("{0}: {1}", idx, item); return item; }).ToList();
Ich bin mit Kommentaren nicht einverstanden, dass eine
for
Schleife in den meisten Fällen die bessere Wahl ist.foreach
ist ein nützliches Konstrukt und kann nicht unterfor
allen Umständen durch eine Schleife ersetzt werden.Wenn Sie beispielsweise einen DataReader haben und alle Datensätze mit einem durchlaufen,
foreach
ruft 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
Dispose
Methode nützlich ist.quelle
foreach
sich Programmers.SE vonfor
(und näher anwhile
) Programmers.SE unterscheidet .Wörtliche Antwort - Warnung: Die Leistung ist möglicherweise nicht so gut wie die Verwendung von a
int
zur Verfolgung des Index. Zumindest ist es besser als zu benutzenIndexOf
.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.
quelle
Mit LINQ, C # 7 und dem
System.ValueTuple
NuGet-Paket können Sie Folgendes tun:Sie können das reguläre
foreach
Konstrukt 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önnenSystem.ValueTuple
.quelle
Mit der Antwort von @ FlySwat habe ich folgende Lösung gefunden:
Sie erhalten den Enumerator mit
GetEnumerator
und dann eine Schleife mit einerfor
Schleife. Der Trick besteht jedoch darin, den Zustand der Schleife herzustellenlistEnumerator.MoveNext() == true
.Da die
MoveNext
Methode 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.quelle
IDisposable
.Es ist nichts Falsches daran, eine Zählervariable zu verwenden. In der Tat, ob Sie verwenden
for
,foreach
while
oderdo
muss 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:
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
Array
und wahrscheinlich auchList<T>
(in der Dokumentation nicht angegeben), jedoch nicht unbedingt für andere Typen (z. B.LinkedList
)):Es sollte niemals notwendig sein, das manuell
IEnumerator
durch AufrufenMoveNext()
und Abfragen zu bedienenCurrent
-foreach
das erspart Ihnen diese besondere Mühe ... Wenn Sie Elemente überspringen müssen, verwenden Sie einfach eincontinue
im 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:
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 eineForEach()
Erweiterungsmethode fürList<T>
undArray
... und es ist nicht klar, dass die Verwendung besser ist als eine einfacheforeach
, da Sie immer noch Single-Threaded für eine hässlichere Syntax ausführen.quelle
Sie können den ursprünglichen Enumerator mit einem anderen umschließen, der die Indexinformationen enthält.
Hier ist der Code für die
ForEachHelper
Klasse.quelle
Hier ist eine Lösung, die ich gerade für dieses Problem gefunden habe
Originalcode:
Code aktualisiert
Verlängerungsmethode:
quelle
Fügen Sie einfach Ihren eigenen Index hinzu. Halte es einfach.
quelle
Es wird nur für eine Liste funktionieren und nicht für eine IEnumerable, aber in LINQ gibt es Folgendes:
@ 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.
quelle
C # 7 gibt uns endlich eine elegante Möglichkeit, dies zu tun:
quelle
Warum foreach ?!
Der einfachste Weg ist die Verwendung von for anstelle von foreach, wenn Sie List verwenden :
Oder wenn Sie foreach verwenden möchten:
Sie können dies verwenden, um den Index jeder Schleife zu kennen:
quelle
Dies würde für unterstützende Sammlungen funktionieren
IList
.quelle
O(n^2)
da in den meisten ImplementierungenIndexOf
istO(n)
. 2) Dies schlägt fehl, wenn die Liste doppelte Elemente enthält.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.quelle
Diese Antwort: Setzen Sie sich beim C # -Sprachteam für direkte Sprachunterstützung ein.
Die führende Antwort lautet:
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
Wenn
foreach ()
verwendet wird undwith var index
vorhanden ist, erwartet der Compiler, dass die Elementauflistung dieIIndexedEnumerable
Schnittstelle 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.Später kann die CLR aktualisiert werden, um eine interne Indexverfolgung zu erhalten, die nur verwendet wird, wenn ein
with
Schlüsselwort angegeben ist und die Quelle nicht direkt implementiert wirdIIndexedEnumerable
Warum:
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
quelle
Wenn es sich bei der Sammlung um eine Liste handelt, können Sie List.IndexOf wie folgt verwenden:
quelle
Verwenden Sie besser eine schlüsselwortsichere
continue
Konstruktion wie diesequelle
Sie können Ihre Schleife folgendermaßen schreiben:
Nach dem Hinzufügen der folgenden Struktur und Erweiterungsmethode.
Die Struktur- und Erweiterungsmethode kapselt die Enumerable.Select-Funktionalität.
quelle
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
quelle
struct
für das Paar (Index, Element) verwenden.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.
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 .
quelle
Ich denke nicht, dass dies ziemlich effizient sein sollte, aber es funktioniert:
quelle
Ich habe dies in LINQPad erstellt :
Sie könnten auch einfach verwenden
string.join
:quelle
n
Elemente gibt, sind die Operationenn * n
odern
-quadratisch. en.wikipedia.org/wiki/…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.
quelle
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.
quelle
Wie wäre es mit so etwas? Beachten Sie, dass myDelimitedString möglicherweise null ist, wenn myEnumerable leer ist.
quelle
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
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 ...
quelle
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.
Dies würde Ihnen ein Wörterbuch mit Namen geben, ob das Element in der Liste ungerade (1) oder gerade (0) war.
quelle