2009-07-23 3 views
3

Gibt es Vorteile für beide Ansätze? Wenn ich List-Elemente durchqueren und eine Aktion für jede ausführen muss, sollte ich den traditionellen foreach-loop-Mechanismus verwenden oder zu List.ForEach wechseln?Wann würde ich die Liste <T> .ForEach über eine native foreach-Schleife verwenden?

Matthew Podwysocki @ CodeBetter.com schrieb einen interessanten Artikel über die anti-for campaign. Das brachte mich dazu, über das Problem nachzudenken, das eine Schleife zu lösen versucht. In diesem Artikel argumentiert Matthew, dass explizite Schleifenstrukturen dazu führen, dass man über das "Wie" statt über das "Was" nachdenkt.

Was sind gute Gründe, einen zu verwenden (wenn es einen gibt)?

Antwort

9

Für eine Sache, würden Sie es verwenden, wenn Sie den Delegierten aus irgendeinem Grund übergeben wurden, um zu gelten. Sie können beispielsweise Ihre eigene Liste erstellen, sie auffüllen usw. und dann einen Delegaten auf jeden Eintrag anwenden. Zu diesem Zeitpunkt schriftlich:

list.ForEach(action); 

ist einfacher als

foreach (Item item in list) 
{ 
    action(item); 
} 
2

Ich sehe nur einen Vorteil, wenn Sie einen vorhandenen Delegaten haben, den Sie weitergeben möchten. ForEach().

In den meisten anderen Fällen, denke ich, eine echte foreach() ist effizienter und lesbarer.

+0

Wirklich? Siehe CIL-Ausgabe unten und Leistungsbenchmark. –

+1

Ich bezweifle die Genauigkeit dieser Benchmarks. Das Beispiel ist zu klein und der Testfall ist nicht repräsentativ für den Code der realen Welt (IMHO) –

+0

Aktualisiert mit realistischerem Code. Die List.ForEach-Methode ist immer noch deutlich schneller. Wenn Sie Vorschläge haben, den Test valider zu machen, lassen Sie es mich wissen. –

1

Ich stimme mit Matthew Podwysocki überein. Wenn Sie keine Delegierten herumreichen und eine Sammlung mit einer von ihnen durchlaufen wollen, würde ich mich an Standard-Schleifenkonstrukte halten.

8

ich List.ForEach deutlich schneller erwiesen. Hier sind die Ergebnisse der letzten vier Läufe des (jetzt überarbeitet) Leistungstest:

NativeForLoop:  00:00:04.7000000 
ListDotForEach:  00:00:02.7160000 
--------------------------------------- 
NativeForLoop:  00:00:04.8660000 
ListDotForEach:  00:00:02.6560000 
--------------------------------------- 
NativeForLoop:  00:00:04.6240000 
ListDotForEach:  00:00:02.8160000 
--------------------------------------- 
NativeForLoop:  00:00:04.7110000 
ListDotForEach:  00:00:02.7190000 

Jeder Test mit hundert Millionen (100.000.000) Iterationen durchgeführt wurde. Ich habe den Test aktualisiert, um eine benutzerdefinierte Klasse (Fruit) zu verwenden, und jede Schleife kann auf ein Mitglied im aktuellen Objekt zugreifen und damit arbeiten. Jede Schleife führt dieselbe Aufgabe aus.

Hier ist die gesamte Quelle der Testklasse:

class ForEachVsClass 
{ 

static Int32 Iterations = 1000000000; 
static int Work = 0; 

public static void Init(string[] args) 
{ 
    if (args.Length > 0) 
     Iterations = Int32.Parse(args[0]); 
    Console.WriteLine("Iterations: " + Iterations); 
} 

static List<Fruit> ListOfFruit = new List<Fruit> { 
    new Fruit("Apple",1), new Fruit("Orange",2), new Fruit("Kiwi",3), new Fruit("Banana",4) }; 


internal class Fruit { 
    public string Name { get; set; } 
    public int Value { get; set; } 
    public Fruit(string _Name, int _Value) { Name = _Name; Value = _Value; } 
} 


[Benchmark] 
public static void NativeForLoop() 
{ 
    for (int x = 0; x < Iterations; x++) 
    { 
     NativeForLoopWork(); 
    } 

} 

public static void NativeForLoopWork() 
{ 
    foreach (Fruit CurrentFruit in ListOfFruit) { 
     Work+=CurrentFruit.Value; 
    } 
} 


[Benchmark] 
public static void ListDotForEach() 
{ 
    for (int x = 0; x < Iterations; x++) 
    { 
     ListDotForEachWork(); 
    } 
} 

public static void ListDotForEachWork() 
{ 
    ListOfFruit.ForEach((f)=>Work+=f.Value); 
} 

}

Hier ist die resultierende IL für die Arbeitsmethoden (extrahiert, um sie leichter lesbar zu machen):

.method public hidebysig static void NativeForLoopWork() cil managed 
{ 
    .maxstack 2 
    .locals init (
     [0] class ForEachVsClass/Fruit CurrentFruit, 
     [1] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<class ForEachVsClass/Fruit> CS$5$0000) 
    L_0000: ldsfld class [mscorlib]System.Collections.Generic.List`1<class ForEachVsClass/Fruit> ForEachVsClass::ListOfFruit 
    L_0005: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> [mscorlib]System.Collections.Generic.List`1<class ForEachVsClass/Fruit>::GetEnumerator() 
    L_000a: stloc.1 
    L_000b: br.s L_0026 
    L_000d: ldloca.s CS$5$0000 
    L_000f: call instance !0 [mscorlib]System.Collections.Generic.List`1/Enumerator<class ForEachVsClass/Fruit>::get_Current() 
    L_0014: stloc.0 
    L_0015: ldsfld int32 ForEachVsClass::Work 
    L_001a: ldloc.0 
    L_001b: callvirt instance int32 ForEachVsClass/Fruit::get_Value() 
    L_0020: add 
    L_0021: stsfld int32 ForEachVsClass::Work 
    L_0026: ldloca.s CS$5$0000 
    L_0028: call instance bool [mscorlib]System.Collections.Generic.List`1/Enumerator<class ForEachVsClass/Fruit>::MoveNext() 
    L_002d: brtrue.s L_000d 
    L_002f: leave.s L_003f 
    L_0031: ldloca.s CS$5$0000 
    L_0033: constrained [mscorlib]System.Collections.Generic.List`1/Enumerator<class ForEachVsClass/Fruit> 
    L_0039: callvirt instance void [mscorlib]System.IDisposable::Dispose() 
    L_003e: endfinally 
    L_003f: ret 
    .try L_000b to L_0031 finally handler L_0031 to L_003f 
} 



.method public hidebysig static void ListDotForEachWork() cil managed 
{ 
    .maxstack 8 
    L_0000: ldsfld class [mscorlib]System.Collections.Generic.List`1<class ForEachVsClass/Fruit> ForEachVsClass::ListOfFruit 
    L_0005: ldsfld class [mscorlib]System.Action`1<class ForEachVsClass/Fruit> ForEachVsClass::CS$<>9__CachedAnonymousMethodDelegate1 
    L_000a: brtrue.s L_001d 
    L_000c: ldnull 
    L_000d: ldftn void ForEachVsClass::<ListDotForEachWork>b__0(class ForEachVsClass/Fruit) 
    L_0013: newobj instance void [mscorlib]System.Action`1<class ForEachVsClass/Fruit>::.ctor(object, native int) 
    L_0018: stsfld class [mscorlib]System.Action`1<class ForEachVsClass/Fruit> ForEachVsClass::CS$<>9__CachedAnonymousMethodDelegate1 
    L_001d: ldsfld class [mscorlib]System.Action`1<class ForEachVsClass/Fruit> ForEachVsClass::CS$<>9__CachedAnonymousMethodDelegate1 
    L_0022: callvirt instance void [mscorlib]System.Collections.Generic.List`1<class ForEachVsClass/Fruit>::ForEach(class [mscorlib]System.Action`1<!0>) 
    L_0027: ret 
} 
+0

Ich bin gespannt, was das Ergebnis für größere Liste wäre. Die Liste enthält nur 4 Elemente. (Übrigens: Danke für die Änderung des Testfalls) –

2

Eric Lippert has come out against IEnumerable.ForEach() und ich kann beide Seiten des Arguments sehen. Nachdem ich sein Argument dagegen beiseite geschoben und implementiert habe, habe ich ein kleines bisschen Freude darin gefunden, wie kurz und leserlich ein paar Codeblöcke gemacht wurden.

Nachdem ich von Nebenwirkungen gebissen worden bin, an die ich normalerweise mit LINQ nicht denken muss, kann ich auch sehen, warum er dafür plädierte, nicht mit ihm zu versenden.

Der Fall mit Delegierten ist ein stärkerer für ForEach(), aber ich denke nicht, dass eine Standard-foreach-Schleife die Absicht verdunkelt.

Ich glaube nicht, dass es definitiv richtige oder falsche Antwort gibt.

0

Manchmal finde ich, dass ich rechts leichter lesbaren Code in einem Lambda-Ausdruck in .ForEach(). Außerdem müssen Sie den Typ, über den Sie iterieren, nicht explizit ausschreiben. Da es stark typisiert ist, weiß der Compiler bereits.

Beispiel:

logs.ForEach(log => 
    { 
     log.DoSomething(); 
    }); 
+1

Vereinbart auf den Lambda-Ausdruck Teil, aber nichts hält Sie davon ab, zu schreiben: foreach (var Log-Protokolle) –

+0

wahr. nur zwei Möglichkeiten, um eine Sache zu tun. Aber Sie können auch einen Delegaten verwenden, und es wird weniger Code. – mkchandler

0

Das Problem der List.ForEach ist das nicht möglich ist oder aus Attribute ref innen zu passieren. In den meisten anderen Fällen ist die Verwendung von List.ForEach besser lesbar.

Verwandte Themen