至于性能,打算写段代码对它们分别测试一下。
var arr = Enumerable.Range(0, 100000000).ToList();
var sw = Stopwatch.StartNew();
for(var i = 0;i < arr.Count; i++){}
sw.Stop();
Console.WriteLine("for loop takes : " + sw.ElapsedTicks);
sw = Stopwatch.StartNew();
foreach(var x in arr){}
sw.Stop();
Console.WriteLine("for loop takes : " + sw.ElapsedTicks);
先看生成的IL for循环的部分:
...
IL_0018: ldc.i4.0
IL_0019: stloc.2 // i
IL_001A: br.s IL_0022
IL_001C: nop
IL_001D: nop
IL_001E: ldloc.2 // i
IL_001F: ldc.i4.1
IL_0020: add
IL_0021: stloc.2 // i
IL_0022: ldloc.2 // i
IL_0023: ldloc.0 // arr
IL_0024: callvirt System.Collections.Generic.List<System.Int32>.get_Count
IL_0029: clt
IL_002B: stloc.s 04 // CS$4$0000
IL_002D: ldloc.s 04 // CS$4$0000
IL_002F: brtrue.s IL_001C
...
除了IL_0024中的callvirt会触发方法虚表查询外,几乎不存在任何高消耗的指令。再来看foreach的IL部分:...
IL_005A: ldloc.0 // arr
IL_005B: callvirt System.Collections.Generic.List<System.Int32>.GetEnumerator
IL_0060: stloc.s 05 // CS$5$0001
IL_0062: br.s IL_006E
IL_0064: ldloca.s 05 // CS$5$0001
IL_0066: call System.Collections.Generic.List<System.Int32>+Enumerator.get_Current
IL_006B: stloc.3 // x
IL_006C: nop
IL_006D: nop
IL_006E: ldloca.s 05 // CS$5$0001
IL_0070: call System.Collections.Generic.List<System.Int32>+Enumerator.MoveNext
IL_0075: stloc.s 04 // CS$4$0000
IL_0077: ldloc.s 04 // CS$4$0000
IL_0079: brtrue.s IL_0064
IL_007B: leave.s IL_008C
IL_007D: ldloca.s 05 // CS$5$0001
IL_007F: constrained. System.Collections.Generic.List<>.Enumerator
IL_0085: callvirt System.IDisposable.Dispose
IL_008A: nop
IL_008B: endfinally
IL_008C: nop
...
首先可以看到很多方法调用,并且在循环体内部每次都要调用方法MoveNext。最后可以看到,有end finally,意味着这个循环外部包了一层try-finally。从IL来看,foreach要比for慢的多了。 现在来看StopWatch的执行结果。
for loop takes : 764538
for loop takes : 1311252
for几乎快了一倍。 结论:编程语言提供的关键字是为了解决某种问题而存在,需要对具体的业务场景进行判断再决定,有关性能方面,一定要拿出数据说话。