关于性能:C#中IEnumerable类的foreach和for循环之间的区别

关于性能:C#中IEnumerable类的foreach和for循环之间的区别

Difference between foreach and for loops over an IEnumerable class in C#

我被告知以下代码块之间存在性能差异。

1
2
3
4
foreach (Entity e in entityList)
{
 ....
}

1
2
3
4
5
for (int i=0; i<entityList.Count; i++)
{
   Entity e = (Entity)entityList[i];
   ...
}

在哪里?

1
List<Entity> entityList;

我不是clr所期望的,但从我所能知道的来看,它们应该归结为基本上相同的代码。有没有人有确凿的证据(见鬼,我会拿包装好的泥土)来证明?


foreach创建枚举器的实例(从getEnumerator返回),该枚举器在foreach循环的整个过程中也保持状态。然后它反复调用枚举器上的next()对象,并为它返回的每个对象运行代码。

它们不会以任何方式归结为相同的代码,实际上,如果编写自己的枚举器,您会看到这一点。


这是一篇很好的文章,展示了两个循环之间的IL差异。

foreach在技术上速度较慢,但更易于使用和阅读。除非性能至关重要,否则我更喜欢foreach循环而不是for循环。


foreach示例大致对应于以下代码:

1
2
3
4
5
6
using(IEnumerator<Entity> e = entityList.GetEnumerator()) {
    while(e.MoveNext()) {
        Entity entity = e.Current;
        ...
    }
}

这里有两个费用,一个普通的for循环不需要支付:

  • EntityList.GetEnumerator()分配枚举器对象的成本。
  • 列表中每个元素的两个虚拟方法调用(moveNext和current)的开销。

  • 这里漏了一点:列表有一个Count属性,它在内部跟踪其中有多少元素。

    IEnumerable没有。

    如果您编程到接口IEnumerable并使用Count扩展方法,它将枚举以计数元素。

    但这是一个无意义的点,因为在IEnumerable中不能按索引引用项。

    因此,如果您想锁定到列表和数组中,您可以获得较小的性能提升。

    如果你想灵活使用foreach和程序到ienumerable。(允许使用LINQ和/或产量返回)。


    在分配方面,最好看一下这个blogpost。它精确地显示了在什么情况下在堆上分配枚举器。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    For Loop
    for loop is used to perform the opreration n times
    for(int i=0;i<n;i++)
    {
    l=i;
    }
    foreach loop

    int[] i={1,2,3,4,5,6}
    foreach loop is used to perform each operation value/object in IEnumarable
    foreach(var k in i)
    {
    l=k;
    }


    我认为一种可能的情况是,如果可枚举类型的大小和循环条件为常量,则可能获得性能提升;例如:

    1
    2
    3
    4
    5
    6
    const int ArraySize = 10;
    int[] values = new int[ArraySize];

    //...

    for (int i = 0; i

    在这种情况下,根据循环体的复杂性,编译器可能能够用内联调用替换循环。我不知道.NET编译器是否会这样做,如果可枚举类型的大小是动态的,那么它的实用程序是有限的。

    foreach可能表现得更好的一种情况是使用数据结构,如链表,其中随机访问意味着遍历链表;foreach使用的枚举器可能一次迭代一个项目,使每个访问O(1)和全循环O(n),但调用索引器意味着从头部开始并在右索引;O(n)每个循环代表O(n^2)。

    就我个人而言,我通常不担心它,在我需要所有项目的任何时候使用foreach,也不关心项目的索引。如果我没有处理所有项目,或者我真的需要知道索引,我使用for。唯一一次我能看到它是一个大问题是像链表这样的结构。


    推荐阅读