IL Discovery 系列 一《 Linq查询“延迟执行”的IL实现》

quark 标签: C# IL

 

这是本系列的第一篇文章,这个系列主要是想和大家分享自己在学习.NET的过程中关于IL语言的一些心得体会。

C#语言在3.0以后,在语言创新上已经上升到了一个新的台阶,扩展方法、匿名函数、Lambda表达式、Linq等等。我们知道,.net framework 3.0/3.5都是基于CLR2.0基础之上的,CLR在功能上并没有任何的提升。上面提到的新的语法特性,在IL语言级别,都会被脱掉“华丽的外衣”,露出其真实的面目——类、类的数据成员、类的成员函数。其他神马都是浮云。

.net程序员是否需要学习IL语言,已经有很多大牛已经讨论过了,这当然取决于个人需求和兴趣,但是个人比较倾向于金旭亮老师的观点:对于一般.net程序员,也有必要了解IL语言。因为阅读IL语言能帮助我们认清楚.net程序在编译时编译器暗地里搞了什么鬼,也能帮助我们认清楚C#,VB.NET语言多态机制的实现原理等等,这有助于我们写出更好的代码

总之、通过分析IL语言,我们可以让C#这些新语法特性,变得不再那么神秘。

Linq查询有一个很重要的特性就是“延迟执行”,即在代码第一次试图遍历该查询结果的时候才执行。

为了搞清楚.net如何实现该机制,我做了如下实验。

下面是一个很简单的静态函数:

        static void MyMethod()
        {
            int[] arr = { 1, 3, 4, };
            var query = arr.Where(num => num > 1);

            foreach (var num in query)
            {
                Console.WriteLine(num);
            }
        }

这个函数创建了一个数组对象,建立一个Linq查询,并且遍历查询结果。

下面我们来看看这个函数所对应的IL语言。

Linq延迟执行

我们看到,对应于上面的MyMethod函数,C#编译器新做了如下工作:

  1. MyMethod成员函数。
  2. 增加了一个静态的Func<Int32, bool>类型的成员CS$<>9__CachedAnonymousMethodDelegate1;
  3. 增加了一个静态成员函数<MyMethod>b__0,该函数接受一个Int32类型参数,返回bool类型;

我们注意到CS$<>9__CachedAnonymousMethodDelegate1的签名同函数<MyMethod>b__0吻合,可以推断两者一定会有关联。

<MyMethod>b__0

该函数的IL代码如下:

  
.method private hidebysig static bool  '<MyMethod>b__0'(int32 num) cil managed
{
  .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
  // Code size       5 (0x5)
  .maxstack  8
  IL_0000:  ldarg.0     // 将0位置参数压入栈 
  IL_0001:  ldc.i4.1    // 将常整数1压入栈
  IL_0002:  cgt         // 比较栈中两数大小,并将结果压入栈
  IL_0004:  ret
} // end of method Program::'<MyMethod>b__0'

通过Reflector工具得到的C#代码如下:

[CompilerGenerated]
private static bool <MyMethod>b__0(int num)
{
    return (num > 1);
}
 

这个函数逻辑很简单——如果参数大于1,返回true,否则返回false;

MyMethod

该函数IL代码相对复杂,为了简便,只列出关键IL代码:

.method private hidebysig static void  MyMethod() cil managed
{
  // Code size       97 (0x61)
  .maxstack  4
  .locals init ([0] int32[] arr,
           [1] class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> query,
           [2] int32 num,
           [3] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> CS$5$0000)
  ... // 初始化数组arr
  IL_0013:  ldsfld     class [mscorlib]System.Func`2<int32,bool> ILDiscovery.Program::'CS$<>9__CachedAnonymousMethodDelegate1' // 将CS$<>9__CachedAnonymousMethodDelegate1的值压入计算堆栈
  IL_0018:  brtrue.s   IL_002b  // 如果CS$<>9__CachedAnonymousMethodDelegate1非空,则跳转到语句IL_002b上
  IL_001a:  ldnull   // 将空指针压入栈
  IL_001b:  ldftn      bool ILDiscovery.Program::'<MyMethod>b__0'(int32)  // 将指向<MyMethod>b__0方法的函数指针压入栈
  IL_0021:  newobj     instance void class [mscorlib]System.Func`2<int32,bool>::.ctor(object,
                                                                                      native int)    // 创建一个新的类型Func<Int32, bool>的对象
  IL_0026:  stsfld     class [mscorlib]System.Func`2<int32,bool> ILDiscovery.Program::'CS$<>9__CachedAnonymousMethodDelegate1'    // 将上面创建的对象赋值给CS$<>9__CachedAnonymousMethodDelegate1
  IL_002b:  ldsfld     class [mscorlib]System.Func`2<int32,bool> ILDiscovery.Program::'CS$<>9__CachedAnonymousMethodDelegate1'    // 将CS$<>9__CachedAnonymousMethodDelegate1的值压入堆栈
  IL_0030:  call       class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::Where<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>,
                                                                                                                                       class [mscorlib]System.Func`2<!!0,bool>)    // 调用System.Linq.Enumerable下的静态方法Where(...)
  IL_0035:  stloc.1    // 将上面调用Where函数的结果赋值给位置1的局部变量query
  IL_0036:  ldloc.1    // 将位置为1的局部变量query压入栈
  IL_0037:  callvirt   instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()    // 调用IEnumerator的接口方法GetEnumerator()
  IL_003c:  stloc.3     // 将结果赋值给位置3的 CS$5$0000

  /* 通过枚举器遍历元素并调用Console.WriteLine()函数输出结果 */
  .try
  {
    IL_003d:  br.s       IL_004c
    IL_003f:  ldloc.3
    IL_0040:  callvirt   instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
    IL_0045:  stloc.2
    IL_0046:  ldloc.2
    IL_0047:  call       void [mscorlib]System.Console::WriteLine(int32)
    IL_004c:  ldloc.3
    IL_004d:  callvirt   instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
    IL_0052:  brtrue.s   IL_003f
    IL_0054:  leave.s    IL_0060
  }  // end .try
  finally
  {
    IL_0056:  ldloc.3
    IL_0057:  brfalse.s  IL_005f
    IL_0059:  ldloc.3
    IL_005a:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_005f:  endfinally
  }  // end handler
  IL_0060:  ret
} // end of method Program::MyMethod

通过这段IL指令,我们证实了上面的推断:CS$<>9__CachedAnonymousMethodDelegate1变量用来存储<MyMethod>b__0函数本地代码的地址。

另外我们还能得出如下结论:

  1. 所谓扩展方法,实际上并没有修改“被扩展的类型”,而是编译器在发现对扩展方法的调用时,自动的将其编译为调用“定义该扩展方法的类型所对应的静态方法”;
  2. 所谓Lambda表达式,就像是匿名函数的简化版本,编译器在编译为IL语言时,会生成一个对应的成员函数。

但是,现在通过以上的内容,我们没有看到任何能够说明Linq查询“延迟执行”的东西,因为在第IL_0030句指令返回了一个IEnumerable<>类型的对象后,在接下的指令中,通过获取该对象的枚举器(IL_0037),然后都是正常遍历一个集合对象的代码。

“延迟执行”究竟是如何实现的??

通过Reflector(不得不说,这玩意儿太好用了)

public override bool MoveNext()
{
    if (base.state == 1)
    {
        while (this.index < this.source.Length)
        {
            TSource arg = this.source[this.index];
            this.index++;
            if (this.predicate(arg))
            {
                base.current = arg;
                return true;
            }
        }
        this.Dispose();
    }
    return false;
}
 

这段代码就是以上MoveNext函数的具体实现,程序实际上是在每次执行MoveNext的时候,才调用this.predicate(arg)函数,而我们之前已经将条件判定函数<MyMethod>b__0指针同过Where方法传给了返回的IEnumerable<int>对象。

这就是Linq“延迟执行的奥秘”——在实际遍历的时候才执行条件判定函数

转载于:https://www.cnblogs.com/quark/archive/2011/06/08/2074963.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值