2009-07-23 103 views
3

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

Matthew Podwysocki @ CodeBetter.com写了一篇关于anti-for campaign的有趣文章。这让我想到了一个循环试图解决的问题。在这篇文章中,马修认为明确的循环结构让你思考'如何'而不是'什么'。

有什么好的理由使用其中一个(如果有的话)?

回答

9

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

list.ForEach(action); 

简单比

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

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

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

+0

真的吗?请参阅下面的CIL输出和性能基准。 – 2009-07-23 15:33:16

+1

我怀疑这些基准的准确性。样本太小,测试案例不代表真实世界代码(恕我直言) – 2009-07-23 15:39:19

+0

用更真实的代码更新。 List.ForEach方法仍然快得多。如果您有任何建议让测试更有效,请告诉我。 – 2009-07-23 16:27:13

1

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

8

我发现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<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); 
} 

}

下面是工作方法所得到的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<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 
} 
2

Eric Lippert has come out against IEnumerable.ForEach()我可以看到双方的论点。把它的论点推到一边并实现它后,我发现了一些小小的欢乐,在简洁和可读性上做了几个代码块。

由于我通常不需要考虑LINQ的副作用,所以我也可以看到他为什么不提供它。

对于ForEach()来说,委托的情况比较强烈,但我不认为标准的foreach循环会模糊所有这些意图。

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

0

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

实施例:

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

List.ForEach的问题是,不能够通过属性ref或out内部。 在大多数其他情况下,我认为使用List.ForEach更具可读性。