这是本系列的第一篇文章,这个系列主要是想和大家分享自己在学习.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语言。
我们看到,对应于上面的MyMethod函数,C#编译器新做了如下工作:
- MyMethod成员函数。
- 增加了一个静态的Func<Int32, bool>类型的成员CS$<>9__CachedAnonymousMethodDelegate1;
- 增加了一个静态成员函数<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函数本地代码的地址。
另外我们还能得出如下结论:
- 所谓扩展方法,实际上并没有修改“被扩展的类型”,而是编译器在发现对扩展方法的调用时,自动的将其编译为调用“定义该扩展方法的类型所对应的静态方法”;
- 所谓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“延迟执行的奥秘”——在实际遍历的时候才执行条件判定函数!