When will I use List <T> .ForEach through my own foreach loop?
Are there any benefits to any approach? If I need to traverse the List elements and perform an action for each of them, should I use the traditional foreach loop mechanism or go to List.ForEach?
Matthew Podwysocki @ CodeBetter.com wrote an interesting anti-campaign article. This made me think about the problem the loop is trying to solve. In this article, Matthew argues that explicit loop structures make you think of βhowβ instead of βwhat.β
What are some good reasons to use one over the other (if any)?
On the one hand, you would use this if a delegate handed you over to apply for any reason. For example, you can create your own list, fill it in, etc., and then apply a delegate to each entry. At this point, by writing:
list.ForEach(action); easier than
foreach (Item item in list) { action(item); } I found List.ForEach much faster. The following are the results of the last four runs of the test (now revised):
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 Each test was performed using one hundred million (100,000,000) iterations. I updated the test to use a custom class (Fruit) and got every loop access and worked with a member inside the current object. Each cycle performs the same task.
Here is the entire source of the test class:
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); } }
Here is the IL result for the working methods (extracted to make them easier to read):
.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 } Eric Lippert opposed IEnumerable.ForEach () , and I see both sides of the argument. Having cast aside his argument against him and implemented it, I found a little bit of joy in how concise and readable he made a few blocks of code.
Being bitten by side effects, which I usually should not think about in LINQ, I can also understand why he did this in order not to send it with him.
The delegate case is stronger for ForEach (), but I don't think the standard foreach loop completely hides the intent.
I do not think that there is a definitively correct or incorrect answer.