我什么时候会在原生的foreach循环中使用List <t> .ForEach?</t>

时间:2009-07-23 15:13:52

标签: c# collections design-patterns

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

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

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

7 个答案:

答案 0 :(得分:10)

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

list.ForEach(action);

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

答案 1 :(得分: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 :(得分:2)

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

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

答案 3 :(得分:2)

Eric Lippert has come out against IEnumerable.ForEach()我可以看到论点的两面。把他的论点推到一边并实现了它,我发现了一些小小的欢乐,它是如何简洁和可读的,它创造了几个代码块。

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

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

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

答案 4 :(得分:1)

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

答案 5 :(得分:0)

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

示例:

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

答案 6 :(得分:0)

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