Keep it Simple.

Listing 1. Here is the IL code for the for loop. Its performance gain is a result of a simpler output when dealing with complex arrays.

void forDemo(StringCollection outputStrings)
   {
      for (int i=0; i < outputStrings.Count; i++)
         Placebo(outputStrings[i]);
   }
   
/ The resulting IL is pretty straightforward to follow

.method private hidebysig instance void  forDemo(class [System]System.Collections.Specialized.StringCollection outputStrings) cil managed
{
  // Code size       31 (0x1f)
  .maxstack  4
  .locals init (int32 V_0)
  IL_0000:  ldc.i4.0
  IL_0001:  stloc.0
  IL_0002:  br.s       IL_0015
  IL_0004:  ldarg.0
  IL_0005:  ldarg.1
  IL_0006:  ldloc.0
  IL_0007:  callvirt   instance string [System]System.Collections.Specialized.StringCollection::get_Item(int32)
  IL_000c:  call       instance void TestLoops::Placebo(string)
  IL_0011:  ldloc.0
  IL_0012:  ldc.i4.1
  IL_0013:  add
  IL_0014:  stloc.0
  IL_0015:  ldloc.0
  IL_0016:  ldarg.1
  IL_0017:  callvirt   instance int32 [System]System.Collections.Specialized.StringCollection::get_Count()
  IL_001c:  blt.s      IL_0004
  IL_001e:  ret
} // end of method TestLoops::forDemo

The equivalent foreach code is…

   void foreachDemo(StringCollection outputStrings)
   {
      foreach (string tempstring in outputStrings)
         Placebo(tempstring);
   }