C # für jede Verbesserung?

9

Ich stoße beim Programmieren häufig darauf, wenn ich einen Loop-Count-Index in einem foreach haben möchte und eine Ganzzahl erstellen, verwenden, inkrementieren usw. Wäre es nicht eine gute Idee, wenn ein Schlüsselwort eingeführt würde Die Anzahl der Schleifen in einem Foreach? Es kann auch in anderen Loops verwendet werden.

Verstößt dies gegen die Verwendung von Schlüsselwörtern, da der Compiler zulässt, dass dieses Schlüsselwort nur in einem Schleifenkonstrukt verwendet wird?

Justin
quelle
7
Sie können es selbst mit einer Erweiterungsmethode tun, siehe die Antwort von Dan Finch: stackoverflow.com/a/521894/866172 (nicht die am besten bewertete Antwort, aber ich denke, es ist die "sauberere")
Jalayn
Perl und solche Sachen wie $ _ kommen mir in den Sinn. Ich hasse das Zeug.
Yam Marcovic
2
Soweit ich über C # weiß, enthält es viele kontextbasierte Schlüsselwörter, sodass dies nicht "gegen die Verwendung von Schlüsselwörtern" verstößt. Wie in einer Antwort unten ausgeführt, möchten Sie wahrscheinlich nur eine for () Schleife verwenden.
Craige
@ Jalayn Bitte poste das als Antwort. Es ist einer der besten Ansätze.
Apoorv Khurasia
@MonsterTruck: Ich denke nicht, dass er sollte. Die wirkliche Lösung wäre, diese Frage nach SO zu migrieren und sie als Duplikat der Frage zu schließen, auf die Jalayn verweist.
Brian

Antworten:

54

Wenn Sie eine Schleifenzahl innerhalb einer foreachSchleife benötigen, verwenden Sie einfach eine reguläre forSchleife. Die foreachSchleife sollte die spezifische Verwendung von forSchleifen vereinfachen. Es hört sich so an, als hätten Sie eine Situation, in der die Einfachheit des foreachnicht mehr von Vorteil ist.

Ryathal
quelle
1
+1 - guter Punkt. Es ist einfach genug, IEnumerable.Count oder object.length mit einer regulären for-Schleife zu verwenden, um Probleme beim Ermitteln der Anzahl der Elemente im Objekt zu vermeiden.
11
@ GlenH7: Abhängig von der Implementierung kann IEnumerable.Countdies teuer sein (oder unmöglich, wenn es sich um eine einmalige Aufzählung handelt). Sie müssen wissen, was die zugrunde liegende Quelle ist, bevor Sie dies sicher tun können.
Binary Worrier
2
-1: Bill Wagner in Effective C # beschreibt, warum man immer bei foreachOver bleiben sollte for (int i = 0…). Er hat ein paar Seiten geschrieben, aber ich kann es in zwei Worten zusammenfassen - Compileroptimierung bei Iteration. Ok, das waren keine zwei Wörter.
Apoorv Khurasia
13
@MonsterTruck: Was auch immer Bill Wagner schrieb, ich habe genug Situationen gesehen, wie die vom OP beschriebene, in denen eine forSchleife besser lesbaren Code liefert (und theoretische Compileroptimierung oft vorzeitige Optimierung ist).
Doc Brown
1
@DocBrown Dies ist nicht so einfach wie eine vorzeitige Optimierung. Ich habe selbst Engpässe in der Vergangenheit identifiziert und diese mithilfe dieses Ansatzes behoben (aber ich gebe zu, dass ich mich mit Tausenden von Objekten in der Sammlung befasst habe). Ich hoffe, bald ein funktionierendes Beispiel veröffentlichen zu können. Inzwischen ist hier Jon Skeet . [Er sagt, dass die Leistungsverbesserung nicht signifikant sein wird, aber das hängt stark von der Sammlung und der Anzahl der Objekte ab].
Apoorv Khurasia
37

Sie können solche anonymen Typen verwenden

foreach (var item in items.Select((v, i) => new { Value = v, Index = i }))
{
    Console.WriteLine("Item value is {0} and index is {1}.", item.Value, item.Index);
}

Dies ähnelt der Python- enumerateFunktion

for i, v in enumerate(items):
    print v, i
Kris Harper
quelle
+1 Oh ... Glühbirne. Ich mag diesen Ansatz. Warum habe ich noch nie daran gedacht?
Phil
17
Wer dies in C # einer klassischen for-Schleife vorzieht, hat eindeutig eine seltsame Meinung zur Lesbarkeit. Dies mag ein ordentlicher Trick sein, aber ich würde so etwas im Produktionsqualitätscode nicht zulassen. Es ist viel lauter als ein klassischer for-Loop und kann nicht so schnell verstanden werden.
Falcon
4
@Falcon, ist dies eine gute Alternative , wenn Sie nicht NUTZEN eine alte Mode for - Schleife (die eine Zählung benötigt). Das sind ein paar Objektsammlungen, an denen ich arbeiten musste ...
Fabricio Araujo
8
@FabricioAraujo: Sicherlich erfordert C # für Schleifen keine Zählung. Soweit ich weiß, sind sie genau die gleichen wie Cs für Schleifen, was bedeutet, dass Sie einen beliebigen booleschen Wert verwenden können, um die Schleife zu beenden. Beispiel: Überprüfen Sie das Ende der Aufzählung, wenn MoveNext false zurückgibt.
Zan Lynx
1
Dies hat den zusätzlichen Vorteil, dass der gesamte Code für die Verarbeitung des Inkrements am Anfang des foreach-Blocks vorhanden ist, anstatt ihn finden zu müssen.
JeffO
13

Ich füge hier nur die Antwort von Dan Finch hinzu , wie gewünscht.

Gib mir keine Punkte, gib Dan Finch Punkte :-)

Die Lösung besteht darin, die folgende generische Erweiterungsmethode zu schreiben:

public static void Each<T>( this IEnumerable<T> ie, Action<T, int> action )
{
    var i = 0;
    foreach ( var e in ie ) action( e, i++ );
}

Welches wird so verwendet:

var strings = new List<string>();
strings.Each( ( str, n ) =>
{
    // hooray
} );
Jalayn
quelle
2
Das ist ziemlich klug, aber ich denke, dass die Verwendung eines normalen für "lesbarer" ist, insbesondere wenn jemand, der den Code am Ende pflegt, in .NET möglicherweise kein großes Ass ist und mit dem Konzept der Erweiterungsmethoden nicht allzu vertraut ist . Ich greife immer noch nach diesem kleinen Code. :)
MetalMikester
1
Könnte jede Seite davon und die ursprüngliche Frage argumentieren - ich stimme der Absicht des Posters zu, es gibt Fälle, in denen foreach besser liest, aber ein Index benötigt wird, aber ich bin immer dagegen, einer Sprache mehr syntaktischen Zucker hinzuzufügen, wenn dies nicht der Fall ist dringend benötigt - Ich habe genau die gleiche Lösung in meinem eigenen Code mit einem anderen Namen - Strings.ApplyIndexed <T> (......)
Mark Mullin
Ich stimme Mark Mullin zu, Sie können beide Seiten argumentieren. Ich dachte, es wäre eine ziemlich clevere Anwendung von Erweiterungsmethoden und sie passte zu der Frage, also teilte ich sie.
Jalayn
5

Ich kann mich irren, aber ich dachte immer, dass der Hauptpunkt der foreach-Schleife darin bestand, die Iteration aus den STL-Tagen von C ++ zu vereinfachen, d. H.

for(std::stltype<typename>::iterator iter = stl_type_instance.begin(); iter != stl_type_instance.end(); iter++)
{
   //dereference iter to use each object.
}

Vergleichen Sie dies mit der Verwendung von IEnumerable in foreach durch .NET

foreach(TypeName instance in DescendentOfIEnumerable)
{
   //use instance to use the object.
}

Letzteres ist viel einfacher und vermeidet viele der alten Fallstricke. Außerdem scheinen sie fast identischen Regeln zu folgen. Zum Beispiel können Sie den IEnumerable-Inhalt nicht ändern, während Sie sich in der Schleife befinden (was viel sinnvoller ist, wenn Sie es auf STL-Weise betrachten). Die for-Schleife hat jedoch häufig einen anderen logischen Zweck als die Iteration.

Jonathan Henson
quelle
4
+1: Nur wenige Leute erinnern sich, dass foreach im Grunde genommen syntaktischer Zucker über dem Iteratormuster ist.
Steven Evers
1
Nun, es gibt einige Unterschiede. Die in .NET vorhandene Zeigermanipulation ist dem Programmierer ziemlich tief verborgen, aber Sie könnten eine for-Schleife schreiben, die dem C ++ - Beispiel sehr ähnlich for(IEnumerator e=collection.GetEnumerator;e.MoveNext();) { ... }
sieht
@KeithS, nicht wenn Sie feststellen, dass alles, was Sie in .NET berühren, das kein strukturierter Typ ist, ein Zeiger ist, nur mit einer anderen Syntax (. Anstelle von ->). Das Übergeben eines Referenztyps an eine Methode entspricht dem Übergeben als Zeiger und das Übergeben als Referenz als Doppelzeiger. Gleiche Sache. Zumindest denkt eine Person, deren Muttersprache C ++ ist, so darüber. So ähnlich wie, lernen Sie Spanisch in der Schule, indem Sie darüber nachdenken, wie die Sprache syntaktisch mit Ihrer eigenen Muttersprache zusammenhängt.
Jonathan Henson
* Werttyp nicht strukturierter Typ. Es tut uns leid.
Jonathan Henson
2

Wenn es sich bei der fraglichen Sammlung um eine handelt IList<T>, können Sie sie einfach formit der Indizierung verwenden:

for (int i = 0; i < collection.Count; i++)
{
    var item = collection[i];
    // …
}

Wenn nicht, dann könnten Sie verwenden Select(), es hat eine Überladung, die Ihnen den Index gibt.

Wenn das auch nicht geeignet ist, denke ich, dass es einfach genug ist, diesen Index manuell zu pflegen. Es sind nur zwei sehr kurze Codezeilen. Das Erstellen eines Schlüsselworts speziell dafür wäre ein Overkill.

int i = 0;
foreach (var item in collection)
{
    // …
    i++;
}
svick
quelle
+1 Wenn Sie einen Index genau einmal in der Schleife verwenden, können Sie auch Folgendes tun foo(++i)oder davon foo(i++)abhängen, welcher Index benötigt wird.
Job
2

Wenn Sie nur wissen, dass es sich bei der Sammlung um eine IEnumerable handelt, Sie jedoch nachverfolgen müssen, wie viele Elemente Sie bisher verarbeitet haben (und damit die Gesamtzahl, wenn Sie fertig sind), können Sie einer grundlegenden for-Schleife ein paar Zeilen hinzufügen:

var coll = GetMyCollectionAsAnIEnumerable();
var idx = 0;
for(var e = coll.GetEnumerator(); e.MoveNext(); idx++)
{
   var elem = e.Current;

   //use elem and idx as you please
}

Sie können einem foreach auch eine inkrementierte Indexvariable hinzufügen:

var i=0;
foreach(var elem in coll)
{
   //do your thing, then...
   i++;
}

Wenn Sie dies eleganter gestalten möchten, können Sie eine oder zwei Erweiterungsmethoden definieren, um diese Details zu "verbergen":

public static void ForEach<T>(this IEnumerable<T> input, Action<T> action)
{
    foreach(T elem in input)
        action(elem);
}

public static void ForEach<T>(this IEnumerable<T> input, Action<T, int> action)
{
    var idx = 0;
    foreach(T elem in input)
        action(elem, idx++); //post-increment happens after parameter-passing
}

//usage of the index-supporting method
coll.ForEach((e, i)=>Console.WriteLine("Element " + (i+1) + ": " + e.ToString()));
KeithS
quelle
1

Das Scoping funktioniert auch, wenn Sie den Block frei von Kollisionen mit anderen Namen halten möchten ...

{ int index = 0; foreach(var el in List) { Console.WriteLine(el + " @ " + index++); } }
Jay
quelle
-1

Dafür benutze ich eine whileSchleife. Schleifen forfunktionieren auch, und viele Leute scheinen sie zu bevorzugen, aber ich verwende sie nur gerne formit etwas, das eine feste Anzahl von Iterationen hat. IEnumerables kann zur Laufzeit generiert werden, whilewas meiner Meinung nach sinnvoller ist. Nennen Sie es eine informelle Codierungsrichtlinie, wenn Sie möchten.

Robotman
quelle