我何时会在原生foreach循环中使用List .ForEach?

两种方法都有优势吗? 如果我需要遍历List项并对每个项执行操作,我应该使用传统的foreach循环机制还是继续使用List.ForEach?

Matthew Podwysocki @ CodeBetter.com写了一篇关于反竞选活动的有趣文章。 这让我想到了循环试图解决的问题。 在本文中,Matthew认为显式循环结构会让你思考“如何”而不是“什么”。

使用一个优于另一个(如果有的话)有什么好的理由?

首先,如果您因任何原因通过代表申请,您将使用它。 例如,您可以创建自己的列表,填充它等,然后将委托应用于每个条目。 那时,写作:

list.ForEach(action); 

比…更简单

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

我发现List.ForEach要快得多。 以下是(现在修订的)性能测试的最后四次运行的结果:

 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 

每次测试都执行了一亿次(100,000,000次)迭代。 我更新了测试以使用自定义类(Fruit)并让每个循环访问并使用当前对象内的成员。 每个循环执行相同的任务。

以下是测试类的完整源代码:

 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 ListOfFruit = new List { 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); } 

}

以下是工作方法的结果IL(提取使其更易于阅读):

 .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 CS$5$0000) L_0000: ldsfld class [mscorlib]System.Collections.Generic.List`1 ForEachVsClass::ListOfFruit L_0005: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator [mscorlib]System.Collections.Generic.List`1::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::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::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 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 ForEachVsClass::ListOfFruit L_0005: ldsfld class [mscorlib]System.Action`1 ForEachVsClass::CS$<>9__CachedAnonymousMethodDelegate1 L_000a: brtrue.s L_001d L_000c: ldnull L_000d: ldftn void ForEachVsClass::b__0(class ForEachVsClass/Fruit) L_0013: newobj instance void [mscorlib]System.Action`1::.ctor(object, native int) L_0018: stsfld class [mscorlib]System.Action`1 ForEachVsClass::CS$<>9__CachedAnonymousMethodDelegate1 L_001d: ldsfld class [mscorlib]System.Action`1 ForEachVsClass::CS$<>9__CachedAnonymousMethodDelegate1 L_0022: callvirt instance void [mscorlib]System.Collections.Generic.List`1::ForEach(class [mscorlib]System.Action`1) L_0027: ret } 

如果您想要传递给.ForEach()的现有委托,我只会看到一个优势。

在大多数其他情况下,我认为使用真正的foreach()更有效,更可读。

Eric Lippert已经出来反对IEnumerable.ForEach() ,我可以看到争论的两个方面。 把它的论点推到一边并实现了它,我发现了一些小小的欢乐,它是如何简洁和可读的,它制作了几个代码块。

由于我通常不需要考虑使用LINQ的副作用,我也可以看出他为什么没有随附发货的情况。

对于ForEach(),委托的情况更强一些,但我不认为标准的foreach循环会使意图模糊不清。

我认为没有任何明确的正确或错误的答案。

我同意Matthew Podwysocki的观点。 除非你传递代表并希望使用其中一个循环遍历集合,否则我会坚持使用标准循环结构。

有时我发现在.ForEach()中的Lambda表达式中,我可以更容易阅读代码。 它还使您不必明确地写出您正在迭代的类型。 由于它是强类型的,编译器已经知道了。

例:

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

List.ForEach的问题是无法在内部传递属性ref或out。 在大多数其他情况下,我认为使用List.ForEach更具可读性。