一.工具介绍
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 | 从计算堆栈的顶部弹出当前值并将其存储到索引 0 处的局部变量列表中。 |
Ldloc.0 | 将索引 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!"字符串弹出栈(堆栈平衡,即进栈出栈数保持一致) |
在预编译的时候”+“在程序中被认为是直接拼接。编译器将”"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) 项目的工程代码
(2) 作者想说的话
本文的目标读者是1年以上的C#开发人员。可能本文写的比较水,但在之后的章节中,难度会增加的很大。写这个文章的目标也是记录自己的学习过程,同时也是对大家的分享,如果有什么不足之处可以反馈给我。这样我也能写出更好的文章,也可以从交流中提高。之后会有IOC,AOP等等,或者是动态代码生成的实现的章节。也会有比较好玩的,例如C#语法糖等等在IL中的解释。希望自己和大家可以抱着一种虚心的态度一起提高,一起学习。最后,本文写于2018年5月6日星期日。
(3) 下一篇
预告:初探IL:从IL看C#语法(二)IL在表达式生成树中的运用技巧