Interesting question over an internal alias today…
Questions:
What is the difference between enumerating an array using for versus foreach ?
What is the recommended practice and why?
Here is an example:
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
for (int i=0; i<assemblies.Length; i++)
DoSomething(assemblies[i]);
foreach (Assembly a in assemblies)
DoSomething(a);
Answer (from one of the perf architects on the CLR team):
The code gen is basically identical... guidance is to use foreach for clarity unless you plan to modify the contents of the array as you go, in which case for is required. Enumerators are not created in the array case. Note this analysis is not applicable to the collection class case, this is for arrays.
For more details see the "Enumeration Overhead" section in http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnpag/html/scalenetchapt05.asp -- searching for "foreach" in that same document will give other illustrations and guidance.
using System;
class Test{ static void Main(String[] args) { for (int i=0; i<args.Length;i++) Console.WriteLine(args[i]);
foreach (String s in args) Console.WriteLine(s); }}
// standard prolog
G_M001_IG01:
IN0038: 000000 push EDIIN0039: 000001 push ESIIN0040: 000002 push EBXIN0041: 000003 mov EDI, ECX // the array gets stored in EDI on a long term basis
G_M001_IG02: ; offs=000005H
// this is the "for" case
// this is all a redundant length check as far as I can tell
G_M001_IG02:IN0001: 000005 xor EBX, EBX // EBX is holding 'i' -- starts at zeroIN0002: 000007 cmp dword ptr [EDI+4], 0 // checking length of array to see if it is negative, it won't beIN0003: 00000B jle SHORT G_M001_IG06
G_M001_IG03: ; offs=00000DH
G_M001_IG03:IN0004: 00000D cmp EBX, dword ptr [EDI+4] // check the loop variable against array lengthIN0005: 000010 jae SHORT G_M001_IG11IN0006: 000012 mov ESI, gword ptr [EDI+4*EBX+12] // fetch the string from the arrayIN0007: 000016 cmp gword ptr [classVar[0x5b9d1994]], 0 // test if the stream has been initializedIN0008: 00001D jne SHORT G_M001_IG05
G_M001_IG04: ; offs=00001FH
G_M001_IG04:IN0009: 00001F mov ECX, 1IN0010: 000024 call System.Console.InitializeStdOutError(bool) // initialize stream
G_M001_IG05: ; offs=000029H
G_M001_IG05:IN0011: 000029 mov ECX, gword ptr [classVar[0x5b9d1994]] // we now have the power to get the stdout stream into ECXIN0012: 00002F mov EDX, ESI // get the string we saved before set it up as an argIN0013: 000031 call dword ptr [(reloc 0x3a80014)]System.IO.TextWriter.WriteLine(ref) // write the stringIN0014: 000037 add EBX, 1 // ebx has 'i' in it remember, upcountIN0015: 00003A cmp dword ptr [EDI+4], EBX // test the current 'i' against the lengthIN0016: 00003D jg SHORT G_M001_IG03 //loop until done
G_M001_IG06: ; offs=00003FH
// this is the "foreach" case
G_M001_IG06:IN0017: 00003F xor ESI, ESI // ESI is holding the loop index -- starts at zero -- will not hurt codegen at allIN0018: 000041 cmp dword ptr [EDI+4], 0 // same redundant length checkIN0019: 000045 jle SHORT G_M001_IG10
G_M001_IG07: ; offs=000047H
G_M001_IG07:IN0020: 000047 cmp ESI, dword ptr [EDI+4] // testing index before entering loop as aboveIN0021: 00004A jae SHORT G_M001_IG11IN0022: 00004C mov EBX, gword ptr [EDI+4*ESI+12]IN0023: 000050 cmp gword ptr [classVar[0x5b9d1994]], 0 // testing if we need to run the .cctor as aboveIN0024: 000057 jne SHORT G_M001_IG09
G_M001_IG08: ; offs=000059H
G_M001_IG08:IN0025: 000059 mov ECX, 1IN0026: 00005E call System.Console.InitializeStdOutError(bool) // call inlined body of .cctor as above
G_M001_IG09: ; offs=000063H
G_M001_IG09:IN0027: 000063 mov ECX, gword ptr [classVar[0x5b9d1994]] // get stream and write string as aboveIN0028: 000069 mov EDX, EBXIN0029: 00006B call dword ptr [(reloc 0x3a8002c)]System.IO.TextWriter.WriteLine(ref)IN0030: 000071 add ESI, 1IN0031: 000074 cmp dword ptr [EDI+4], ESI // same loop dynamics as above only using ESI instead of EBXIN0032: 000077 jg SHORT G_M001_IG07
G_M001_IG10: ; offs=000079H
G_M001_IG10:IN0033: __epilog: 000079 pop EBX // we are so outta here 00007A pop ESI 00007B pop EDIIN0034: 00007C ret
Notice that this is very specific for arrays (and in V1.1 and above enumerating over strings), this does not apply to enumerating over collections generally… Kevin has a good post on that…