IL Discovery 系列 二《 foreach语句的技术内幕》

今天在阅读《CLR via C#》,书中说

“使用foreach语句时,会在finally块中调用IEnumerator对象的Dispose方法”(第20章 异常和状态管理,P434)

自己很奇怪的是,在编译foreach语句时也会生成try/finally语句块吗?会吧,不会吧!!

于是自己做了如下实验:

        static void ILDiscoveryForeach()
        {
            var someList = Enumerable.Range(0, 10);
            foreach (var item in someList)
            {

            }
        }

使用ILDasm工具得到的IL语言如下:

.method private hidebysig static void  ILDiscoveryForeach() cil managed
{
  // Code size       46 (0x2e)
  .maxstack  2
  .locals init ([0] class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> someList,
           [1] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> CS$5$0000)
  IL_0000:  ldc.i4.0
  IL_0001:  ldc.i4.s   10
  IL_0003:  call       class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32,
                                                                                                                                  int32)
  IL_0008:  stloc.0
  IL_0009:  ldloc.0
  IL_000a:  callvirt   instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()   // 得到someList对象的枚举器
  IL_000f:  stloc.1
  .try
  {
    IL_0010:  br.s       IL_0019    // 无条件跳转到19行
    IL_0012:  ldloc.1    // 将枚举器压入堆栈
    IL_0013:  callvirt   instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()    // 调用枚举器的get_Current()方法
    IL_0018:  pop    // 
    IL_0019:  ldloc.1    // 将枚举器压入堆栈 
    IL_001a:  callvirt   instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()    // 调用MoveNext()方法
    IL_001f:  brtrue.s   IL_0012    // 如果返回值为true,则到12行
    IL_0021:  leave.s    IL_002d    // 如果遍历结束,则退出try语句块,到2d行,但之前会执行finally语句块的内容
  }  // end .try
  finally
  {
    // 如果枚举器不为空,则调用枚举器的Dispose()方法!
    IL_0023:  ldloc.1
    IL_0024:  brfalse.s  IL_002c
    IL_0026:  ldloc.1
    IL_0027:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_002c:  endfinally
  }  // end handler
  IL_002d:  ret
} // end of method Program::ILDiscoveryForeach

从上面可以知道,Jeffery说的是对的,Dispose不是Enumerable对象,而是Enumerator对象!!
 
这样一切都很合理了。
 
但是对于一个数组对象的foreach遍历又是怎样的呢?
代码:
        static void ILDiscoveryForeach()
        {
            int[] someList = { 1, 2, 3, 4, 5 };
            foreach (var item in someList)
            {

            }
        }

IL:

.method private hidebysig static void  ILDiscoveryForeach() cil managed
{
  // Code size       39 (0x27)
  .maxstack  3
  .locals init ([0] int32[] someList,
           [1] int32[] CS$6$0000,
           [2] int32 CS$7$0001)
  IL_0000:  ldc.i4.5
  IL_0001:  newarr     [mscorlib]System.Int32
  IL_0006:  dup
  IL_0007:  ldtoken    field valuetype '<PrivateImplementationDetails>{90173B59-8615-4757-89A1-22E0F30C78E3}'/'__StaticArrayInitTypeSize=20' '<PrivateImplementationDetails>{90173B59-8615-4757-89A1-22E0F30C78E3}'::'$$method0x6000001-1'
  IL_000c:  call       void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(class [mscorlib]System.Array,
                                                                                                      valuetype [mscorlib]System.RuntimeFieldHandle)
  IL_0011:  stloc.0
  IL_0012:  ldloc.0    // 将数组对象压入栈
  IL_0013:  stloc.1    // 将数组指针赋值给位置1的变量
  IL_0014:  ldc.i4.0   // 常数0压入栈
  IL_0015:  stloc.2    // 赋值给位置2变量
  IL_0016:  br.s       IL_0020
  IL_0018:  ldloc.1
  IL_0019:  ldloc.2
  IL_001a:  ldelem.i4
  IL_001b:  pop
  IL_001c:  ldloc.2
  IL_001d:  ldc.i4.1
  IL_001e:  add
  IL_001f:  stloc.2
  IL_0020:  ldloc.2
  IL_0021:  ldloc.1
  IL_0022:  ldlen
  IL_0023:  conv.i4
  IL_0024:  blt.s      IL_0018
  IL_0026:  ret
} // end of method Program::ILDiscoveryForeach

阅读上面的代码,发现对于整数数组对象的foreach,编译器会编译成for(int i=0; i<length;i++)的方式编译。
 
  
原文地址:https://www.cnblogs.com/quark/p/2093227.html