初探IL:从IL看C#语法(一)使用ILSpy和ILDasm的使用


一.工具介绍

1. ILSpy.exe(点击下载):用来查看IL代码;

2. ILDasm(点击下载):看.net Framework中的程序集中方法的源码(通过反编译),即BCL中的代码; 

工具在开发工具中有,也可以从上面的连接链接中下载。


二.C#程序的编译过程

1. 预编译:从C#代码编译为MSIL中间语言代码的过程;

2. 即时编译(JIT):从MSIL中间语言代码编译为机器代码的过程;


三.简单的实践

1. 从"+"号看预编译本质

(1)C#源代码

string result = "Hello" + "World!";
Console.Write(result);

(2)将工程Debug中EXE用ILDasm打开

打开后如下图一:


图一

<1> 条目与原项目的对应关系

说明:对应关系如下图二:


图二

(3)逐条讲解

<1> 程序集清单(MANIFEST)

说明:程序集清单,说明了项目的一些情况,里面有版本信息和共钥信息等等,只需做了解。如下图三:

图三

<2> 类(Class)的声明

说明:如下图四,其中红色方框圈出的条目.class开头的为program类的声明。点击打开后可以看到,这个类是继承自System.Object。


图四

<3> 构造函数(默认构造函数)

说明:如下图五可以看到一个构造函数。虽然我们在program里面并有写任何有关构造函数的代码,但是编译器在编译的时候,自动给我们加了一个默认的构造函数。从打开后的界面可以看到"call instace void [mscorlib]System.Object::ctor()",也就是说这个构造函数调用父类的构造函数。


图五

<4> Main函数

说明:可以看到在右边的条目中,这个函数的左边方框里面有一个“s”的符号,这说明这个函数是静态的函数。打开后里面的内容如图六:

 

图六

(4)Main函数中的方法讲解

<1> 查看IL指令

说明一:在图六中红色方框内部,有很多IL的指令,我们可以通过上网查找来找到这些指令对应的含义。    

点击进入指令查看网站

说明二:下面列出图六中红色框内的指令含义。

IL指令

说明

Ldstr

推送对元数据中存储的字符串的新对象引用。

Stloc.0

从计算堆栈的顶部弹出当前值并将其存储到索引 处的局部变量列表中。

Ldloc.0 

将索引 处的局部变量加载到计算堆栈上。

Call

调用由传递的方法说明符指示的方法。

Ret

从当前方法返回,并将返回值(如果存在)从调用方的计算堆栈推送到被调用方的计算堆栈上。

<2> 程序IL代码分析

说明:图六中红框内的代码的作用如下表格解释:

代码

解释

Ldstr "HelloWorld!"

将一个"HelloWorld!"字符串进行压栈

Stloc.0  

将栈顶的"HelloWorld!"字符串方法局部的0号变量中

Ldloc.0 

0号变量进行压栈,此时栈顶为0号变量,即为"HelloWorld!"字符串

Call void [mscorlib]System.Console::Write(string)

调用Console.Write函数,并且将栈顶的"HelloWorld!"字符串进行传入,执行完函数之后将栈顶的"HelloWorld!"字符串弹出栈(堆栈平衡,即进栈出栈数保持一致)

(3)论一

在预编译的时候”+“在程序中被认为是直接拼接。编译器将”"Hello" + "World!"”优化为了”"HelloWorld!"”,而不是在IL中进行拼接处理。

(4)修改代码实验

说明:我们对原来的代码进行修改。如下:

string value1 = "Hello";
string value2 = "World!";
string ruselt = value1 + value2;
Console.Write(ruselt);

(5)重新编译后用ILDasm打开新生成的EXE

说明:打开后找到Main函数。查看IL代码,如图七:


图七

(6)分析

<1>代码功能

说明:不难通过上面类似的分析得到如下表格功能。这个请读者尝试自己分析。

代码块

完成的功能

第一个红框

将两个字符串进行拼装,用的是string string.Concat(string,string)方法

第二个红框

将字符串显示了

<2>问

说明一:我们发现同样的“+”号在不同的用发下出现不同的编译后IL指令。

说明二:我们还可以直到,这个代码等同于:

string value1 = "Hello";
string value2 = "World!";
string ruselt = string.Concat(value1, value2);
Console.Write(ruselt);

(7)结论二

<1> 两个字符串的相加,被预编译器优化成了字符串的拼接;

<2> 两个字符串变量的相加,被预编译器编译成了对“string.Concat(string,string)”函数的调用;



2. 用ILSpy查看刚才的string.Concat函数

(1)打开ILDasm

说明:打开如下图八如所示:


图八

(2) 找到string类中的Cocat(string,string)方法

说明:如下图九如所示。可以看到传入的string如果为null,在方法内部会转换成string.Empty,所以不用担心如果传入Concat的参数为null的时候会报错的问题。


图九


四.不能算总结的总结

(1) 用ILDasm查看了在不同情况下编译器对“+”的编译。通过对MSIL分析,我们初识了这个神奇的中间语言;

(2) 用ILSpy看了string.Concat函数的源代码(反编译代码),这样就可以查看C#一些函数的内部写法。 


五.写在最后

(1) 项目的工程代码

下载上面的工程代码(环境:vs2010)

(2) 作者想说的话

本文的目标读者是1年以上的C#开发人员。可能本文写的比较水,但在之后的章节中,难度会增加的很大。写这个文章的目标也是记录自己的学习过程,同时也是对大家的分享,如果有什么不足之处可以反馈给我。这样我也能写出更好的文章,也可以从交流中提高。之后会有IOC,AOP等等,或者是动态代码生成的实现的章节。也会有比较好玩的,例如C#语法糖等等在IL中的解释。希望自己和大家可以抱着一种虚心的态度一起提高,一起学习。最后,本文写于2018年5月6日星期日。

(3) 下一篇

预告:初探IL:从IL看C#语法(二)IL在表达式生成树中的运用技巧



猜你喜欢

转载自blog.csdn.net/zsllovemiwa/article/details/80217969