• 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“延迟执行的奥秘”——在实际遍历的时候才执行条件判定函数

  • 相关阅读:
    《活在恩典中》第一章 人类的两难困境
    《真正的修行》把你内心的一切都呈现出来
    Mysql:Plugin:clone=mysql_clone:as of 8.0.17
    Mysql:--init-file && --init-connect
    Mysql:8.0.19:Upgrading Mysql:升级
    Android开发自定义View
    Android控制UI界面
    Android的视图(View)组件
    对Android应用签名
    Android Application的基本组件介绍
  • 原文地址:https://www.cnblogs.com/quark/p/2074963.html
Copyright © 2020-2023  润新知