zoukankan      html  css  js  c++  java
  • 从IL认识关键字(一)

    背景

          网上流传“没有用.NET Reflector反编译并阅读过代码的程序员不是专业的.NET程序员” 。虽然是夸张手法,但是.NET Reflector确实是.Net程序员必不可少的一个工具。但是最近7.0后版本开始收费,功能是强大了,可以直接在VS上反编译看源代码。还有更多的功能没深入研究,但是再多的功能也抵不过编译成.net代码和IL。

    关键字

         第一篇先来研究foreach这个关键字,可能大家都很熟悉,这个关键字的原理。但是既然要整理研究,就系统的整理一遍。若是已经熟悉这个关键字,可以返回。

    集合遍历

    C# 代码, 准备一个Student类,里面只有ID,Name属性

    IList<Student> students = new List<Student>();
    
    foreach (Student stu in students)
    {
       Console.WriteLine(stu.Name);
    }
      

    由于IL代码较多,只贴上部分重要代码

    L_0009: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> [mscorlib]System.Collections.Generic.IEnumerable`1<class Foreach.Student>::GetEnumerator()
    L_000e: stloc.2
    L_000f: br.s L_0026
    L_0011: ldloc.2
    L_0012: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1<class Foreach.Student>::get_Current()
    L_0017: stloc.1
    L_0018: nop
    L_0019: ldloc.1
    L_001a: callvirt instance string Foreach.Student::get_Name()
    L_001f: call void [mscorlib]System.Console::WriteLine(string)
    L_0024: nop
    L_0025: nop
    L_0026: ldloc.2
    L_0027: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
    L_002c: stloc.3
    L_002d: ldloc.3
    L_002e: brtrue.s L_0011
    L_0030: leave.s L_0042
    L_0032: ldloc.2
    L_0033: ldnull
    L_0034: ceq
    L_0036: stloc.3
    L_0037: ldloc.3
    L_0038: brtrue.s L_0041
    L_003a: ldloc.2
    L_003b: callvirt instance void [mscorlib]System.IDisposable::Dispose()

    .try L_000f to L_0032 finallyhandler L_0032 to L_0042

     从IL代码( L_0009   -- L_0030) 看到foreach其实就是一个Enumerator枚举器的遍历。

    翻译后C#代码

    IList<Student> students = new List<Student>();
    
    IEnumerator<Student> enumerator = students.GetEnumerator();
    try
    {
        while (enumerator.MoveNext())
        {
            Student stu = enumerator.Current as Student;
            Console.WriteLine(stu.Name);
        }
    }
    finally
    {
        enumerator.Dispose();
    }

     翻译后代码已与重新反编译成IL与foreach的IL几乎一致

    (注:foreach生成的IL,不仅foreach,还有其他关键字。会比直接写代码多了一些nop指令,网上查阅查阅这个指令的意义是 Do nothing.既然是Do nothing即不影响程序运行,我认为可以忽略)

    验证代码

       虽然说可以直接将IL代码翻译过来,但是我们还要保持一颗怀疑的态度,包括怀疑自己。下面是验证代码:

       1)自己实现IEnumerator,IEnumerable类,输出Current属性与MoveNext()

       下面准备一个Students类实现IEnumerator,IEnumerable接口

       关于IEnumerator和IEnumerable的区别,网上也有很多解释,我的理解大约就是

       IEnumerable:实现IEnumerable才能实现枚举 

       IEnumerator:实现一个枚举器

       

    View Code
    class Students : IEnumerator<Student>, IEnumerable<Student>
        {
            private static List<Student> list = new List<Student>();
            static Students()
            {
                list.Add(new Student() { ID = 1, Name = "Jack" });
                list.Add(new Student() { ID = 2, Name = "Rose" });
            }
    
            private int index = 0;
            private Student current;
    
            #region IEnumerator接口
    
            public Student Current
            {
                get
                {
                    Console.WriteLine("----------Current----------");
                    return this.current;
                }
            }
    
            object System.Collections.IEnumerator.Current
            {
                get { return this.current; }
            }
    
            public bool MoveNext()
            {
                Console.WriteLine("----------MoveNext----------");
                if (this.index < list.Count)
                {
                    this.current = list[this.index];
                    this.index++;
                    return true;
                }
                return false;
            }
    
            public void Reset()
            {
                this.index = 0;
                this.current = null;
            }
    
            public void Dispose()
            {
    
            }
    
            #endregion
    
            #region IEnumerable接口
    
            public IEnumerator<Student> GetEnumerator()
            {
                return this;
            }
    
            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return this;
            }
    
            #endregion
        }

     运行代码截图如下,和我们之前预想一致

     

    数组遍历

       经过园友提醒,foreach漏了还有数组的遍历,数组遍历也是我们平常最常用的。在这里加上数组的遍历。下面是简单一个例子

    public void EeachArray()
    {
        foreach(int item in nums)
        {
        }
    }

      对应的IL如下:

    .locals init (
        [0] int32 num,
        [1] int32[] numArray,
        [2] int32 num2,
        [3] bool flag)
    L_0000: nop 
    L_0001: nop 
    L_0002: ldarg.0 
    L_0003: ldfld int32[] Test.Program::nums
    L_0008: stloc.1 
    L_0009: ldc.i4.0 
    L_000a: stloc.2 
    L_000b: br.s L_0017
    L_000d: ldloc.1 
    L_000e: ldloc.2 
    L_000f: ldelem.i4 
    L_0010: stloc.0 
    L_0011: nop 
    L_0012: nop 
    L_0013: ldloc.2 
    L_0014: ldc.i4.1 
    L_0015: add 
    L_0016: stloc.2 
    L_0017: ldloc.2 
    L_0018: ldloc.1 
    L_0019: ldlen 
    L_001a: conv.i4 
    L_001b: clt 
    L_001d: stloc.3 
    L_001e: ldloc.3 
    L_001f: brtrue.s L_000d
    L_0021: ret 

       这里分为三个部分

    1. (L_0000  --  L_000b) : 初始化变量,跳转到步骤3
    2. (L_000d  --  L_0016) : 取出数组索引为局部变量索引2(即num2)的值并赋值局部变量索引为1(即num),局部变量索引为2(即num2) 加一
    3. (L_0017  --  L_001f)  : 局部变量索引为2(即num2) 与 数组长度比较,若小于跳转步骤2,否则结束

      从上面分析可知,这是一个循环结构,并在循环体取出数组值,上面IL大概是下面形式:

    for(int num1 = 0; num1 < array.Length; num1++)
    {
        int num= array[num1];
    }

    延伸

       我们知道除了上面那种方法,遍历枚举外,还有一种常用的方法遍历枚举

    public IEnumerator<Student> GetEnumerator()
    {
        for (int i = 0; i < list.Count; i++)
        {
            yield return list[i];
        }
    }

     其实yield字段会自动生成一个实现IEnumerator类,所以这种方法最终的枚举器还是IEnumerator,这只是.Net的语法糖

    下一篇关键字

          既然这里提到yield关键字,下一篇写yield的关键字。关于yield园子里的老赵已经详细解释过人肉反编译使用yield关键字的方法。不敢班门弄斧,只是做一个完整的系列,所以按照自己理解的再整理一次。

  • 相关阅读:
    【转】Centos yum 换源
    centos7下使用yum安装mysql
    【转】简易smtp调用类
    【转】Beanstalkd 队列简易使用
    【转】mysql 拖库写库用法
    【转】scp 命令格式
    【转】mac os 安装php
    pip 国内源 gem 国内源
    【转】25个必须记住的SSH命令
    NHibernate 有好几种数据库查询方式
  • 原文地址:https://www.cnblogs.com/WilsonPan/p/2889311.html
Copyright © 2011-2022 走看看