为了账号安全,请及时绑定邮箱和手机立即绑定

C#中“ for”和“ foreach”控制结构的性能差异

C#中“ for”和“ foreach”控制结构的性能差异

C#
jeck猫 2019-12-12 14:10:11
哪个代码段可以提供更好的性能?以下代码段是用C#编写的。1。for(int counter=0; counter<list.Count; counter++){    list[counter].DoSomething();}2。foreach(MyType current in list){    current.DoSomething();}
查看完整描述

3 回答

?
慕桂英3389331

TA贡献2036条经验 获得超8个赞

好吧,部分取决于的确切类型list。它还取决于您使用的确切CLR。


它是否有意义,将取决于您是否在循环中进行任何实际工作。在几乎所有情况下,对性能的影响都不是很大,但对可读性的影响则有利于foreach循环。


我个人也会使用LINQ来避免出现“ if”:


foreach (var item in list.Where(condition))

{

}

编辑:对于那些声称对List<T>with进行迭代foreach产生与for循环相同代码的人来说,以下证据表明它不会:


static void IterateOverList(List<object> list)

{

    foreach (object o in list)

    {

        Console.WriteLine(o);

    }

}

产生IL:


.method private hidebysig static void  IterateOverList(class [mscorlib]System.Collections.Generic.List`1<object> list) cil managed

{

  // Code size       49 (0x31)

  .maxstack  1

  .locals init (object V_0,

           valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object> V_1)

  IL_0000:  ldarg.0

  IL_0001:  callvirt   instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<object>::GetEnumerator()

  IL_0006:  stloc.1

  .try

  {

    IL_0007:  br.s       IL_0017

    IL_0009:  ldloca.s   V_1

    IL_000b:  call       instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object>::get_Current()

    IL_0010:  stloc.0

    IL_0011:  ldloc.0

    IL_0012:  call       void [mscorlib]System.Console::WriteLine(object)

    IL_0017:  ldloca.s   V_1

    IL_0019:  call       instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object>::MoveNext()

    IL_001e:  brtrue.s   IL_0009

    IL_0020:  leave.s    IL_0030

  }  // end .try

  finally

  {

    IL_0022:  ldloca.s   V_1

    IL_0024:  constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<object>

    IL_002a:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()

    IL_002f:  endfinally

  }  // end handler

  IL_0030:  ret

} // end of method Test::IterateOverList

编译器对数组的处理方式不同,foreach基本上将for循环转换为循环,但不是List<T>。这是数组的等效代码:


static void IterateOverArray(object[] array)

{

    foreach (object o in array)

    {

        Console.WriteLine(o);

    }

}


// Compiles into...


.method private hidebysig static void  IterateOverArray(object[] 'array') cil managed

{

  // Code size       27 (0x1b)

  .maxstack  2

  .locals init (object V_0,

           object[] V_1,

           int32 V_2)

  IL_0000:  ldarg.0

  IL_0001:  stloc.1

  IL_0002:  ldc.i4.0

  IL_0003:  stloc.2

  IL_0004:  br.s       IL_0014

  IL_0006:  ldloc.1

  IL_0007:  ldloc.2

  IL_0008:  ldelem.ref

  IL_0009:  stloc.0

  IL_000a:  ldloc.0

  IL_000b:  call       void [mscorlib]System.Console::WriteLine(object)

  IL_0010:  ldloc.2

  IL_0011:  ldc.i4.1

  IL_0012:  add

  IL_0013:  stloc.2

  IL_0014:  ldloc.2

  IL_0015:  ldloc.1

  IL_0016:  ldlen

  IL_0017:  conv.i4

  IL_0018:  blt.s      IL_0006

  IL_001a:  ret

} // end of method Test::IterateOverArray

有趣的是,我在任何地方都找不到C#3规范中记录的内容...



查看完整回答
反对 回复 2019-12-13
?
烙印99

TA贡献1829条经验 获得超13个赞

一个for循环被编译的代码大约相当于这个:


int tempCount = 0;

while (tempCount < list.Count)

{

    if (list[tempCount].value == value)

    {

        // Do something

    }

    tempCount++;

}

其中,将foreach循环编译为大致等效于此的代码:


using (IEnumerator<T> e = list.GetEnumerator())

{

    while (e.MoveNext())

    {

        T o = (MyClass)e.Current;

        if (row.value == value)

        {

            // Do something

        }

    }

}

正如您所看到的,这都取决于枚举器的实现方式以及列表索引器的实现方式。事实证明,基于数组的类型的枚举数通常是这样写的:


private static IEnumerable<T> MyEnum(List<T> list)

{

    for (int i = 0; i < list.Count; i++)

    {

        yield return list[i];

    }

}

正如您所看到的,在这种情况下,它并没有太大的区别,但是链表的枚举数可能看起来像这样:


private static IEnumerable<T> MyEnum(LinkedList<T> list)

{

    LinkedListNode<T> current = list.First;

    do

    {

        yield return current.Value;

        current = current.Next;

    }

    while (current != null);

}

在.NET中,您会发现LinkedList <T>类甚至没有索引器,因此您将无法在链表上进行for循环;但是如果可以的话,索引器的编写必须像这样:


public T this[int index]

{

       LinkedListNode<T> current = this.First;

       for (int i = 1; i <= index; i++)

       {

            current = current.Next;

       }

       return current.value;

}

如您所见,在循环中多次调用此方法要比使用可以记住它在列表中位置的枚举器慢得多。



查看完整回答
反对 回复 2019-12-13
  • 3 回答
  • 0 关注
  • 259 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信