从IL认识关键字(一)

摘要:
ldloc.2L _ 0012:0[mscorlib]System.Collections.Generic.IEnumerator`1&lt:get_ Current()L_0017:get_ Name()L_ 001f:WriteLine(字符串)L_0024:ldloc.2L_0033:ldloc.2L_003b:已翻译的C#代码IList&lt:students=newList&lt:

背景

      网上流传“没有用.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:实现一个枚举器

   

从IL认识关键字(一)第1张从IL认识关键字(一)第2张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
    }

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

 从IL认识关键字(一)第3张

数组遍历

   经过园友提醒,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关键字的方法。不敢班门弄斧,只是做一个完整的系列,所以按照自己理解的再整理一次。

免责声明:文章转载自《从IL认识关键字(一)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇JSP 表单处理如何把一个对象的颜色设置成 ByLayer下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Qt设置创建部分半透明,上面控件不透明

//头文件#pragma once #include <QWidget> #include "ui_widgetFullAD.h" class widgetFullAD : public QWidget { Q_OBJECT public: widgetFullAD(QWidget *parent = Q_NULLPTR)...

APM系统SkyWalking介绍

  公司最近在构建服务化平台,需要上线APM系统,本篇文章简单的介绍SkyWalking APM APM全称Application Performance Management应用性能管理,目的是通过各种探针采集数据,收集关键指标,同时搭配数据呈现以实现对应用程序性能管理和故障管理的系统化解决方案 Zabbix、Premetheus、open-falco...

第五节:STM32输入捕获(用CubeMX学习STM32)

输入捕获学习 《用CubeMX学习STM32》 注释 点击上面蓝字进入完整专栏,这个系列所有文章都会整合到这个专栏 5、STM32定时器输入捕获 前言: STM32定时器输入捕获简介 STM32的输入捕获可以用于捕获脉宽, 测量时间 . 例如超声波测距模块就是需要用输入捕获功能, 通过测量输入脉冲的高电平脉宽 , 从而计算出测量物体的...

Vue中使用openlayer做风场图

<template> <div class="box"> <div ref="emap" id="map"></div> <div id="popup" class="ol-popup"> <a href="#" id="popup-closer" class...

安装phpldapadmin

1. 安装软件 [root@ ~]# yum -y install epel-release [root@ ~]# yum install -y phpldapadmin 2. 配置phpldapadmin [root@ ~]# vi /etc/httpd/conf.d/phpldapadmin.conf # # Web-based tool for...

Linux 环境下安装rlwrap工具

rlwrap项目是一个“readline包装器”,它使用GNU readline库来编辑任何其他命令的键 盘输入。通过rlwrap可以进行命令的上下切换,类似历史命令。 1、下载rlwrap rpm rlwrap rpm包可以通过:https://centos.pkgs.org/  获取   安装这个包可以解决下面截图问题 rlwrap能解决方向键上下切...