您的位置:首页 > 其它

IL Discovery 系列 一《 Linq查询“延迟执行”的IL实现》

2011-06-08 11:07 134 查看
quark标签:C#IL

这是本系列的第一篇文章,这个系列主要是想和大家分享自己在学习.NET的过程中关于IL语言的一些心得体会。

C#语言在3.0以后,在语言创新上已经上升到了一个新的台阶,扩展方法、匿名函数、Lambda表达式、Linq等等。我们知道,.netframework3.0/3.5都是基于CLR2.0基础之上的,CLR在功能上并没有任何的提升。上面提到的新的语法特性,在IL语言级别,都会被脱掉“华丽的外衣”,露出其真实的面目——类、类的数据成员、类的成员函数。其他神马都是浮云。

.net程序员是否需要学习IL语言,已经有很多大牛已经讨论过了,这当然取决于个人需求和兴趣,但是个人比较倾向于金旭亮老师的观点:对于一般.net程序员,也有必要了解IL语言。因为阅读IL语言能帮助我们认清楚.net程序在编译时编译器暗地里搞了什么鬼,也能帮助我们认清楚C#,VB.NET语言多态机制的实现原理等等,这有助于我们写出更好的代码

总之、通过分析IL语言,我们可以让C#这些新语法特性,变得不再那么神秘。

Linq查询有一个很重要的特性就是“延迟执行”,即在代码第一次试图遍历该查询结果的时候才执行。

为了搞清楚.net如何实现该机制,我做了如下实验。

下面是一个很简单的静态函数:

staticvoidMyMethod() { int[]arr={1,3,4,}; varquery=arr.Where(num=>num>1); foreach(varnuminquery) { 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代码如下:


.methodprivatehidebysigstaticbool'<MyMethod>b__0'(int32num)cilmanaged
{
.custominstancevoid[mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()=(01000000)
//Codesize5(0x5)
.maxstack8
IL_0000:ldarg.0//将0位置参数压入栈
IL_0001:ldc.i4.1//将常整数1压入栈
IL_0002:cgt//比较栈中两数大小,并将结果压入栈
IL_0004:ret
}//endofmethodProgram::'<MyMethod>b__0'

通过Reflector工具得到的C#代码如下:

[CompilerGenerated] privatestaticbool<MyMethod>b__0(intnum) { return(num>1); }

这个函数逻辑很简单——如果参数大于1,返回true,否则返回false;

MyMethod
该函数IL代码相对复杂,为了简便,只列出关键IL代码:

.methodprivatehidebysigstaticvoidMyMethod()cilmanaged
{
//Codesize97(0x61)
.maxstack4
.localsinit([0]int32[]arr,
[1]class[mscorlib]System.Collections.Generic.IEnumerable`1<int32>query,
[2]int32num,
[3]class[mscorlib]System.Collections.Generic.IEnumerator`1<int32>CS$5$0000)
...//初始化数组arr
IL_0013:ldsfldclass[mscorlib]System.Func`2<int32,bool>ILDiscovery.Program::'CS$<>9__CachedAnonymousMethodDelegate1'//将CS$<>9__CachedAnonymousMethodDelegate1的值压入计算堆栈
IL_0018:brtrue.sIL_002b//如果CS$<>9__CachedAnonymousMethodDelegate1非空,则跳转到语句IL_002b上
IL_001a:ldnull//将空指针压入栈
IL_001b:ldftnboolILDiscovery.Program::'<MyMethod>b__0'(int32)//将指向<MyMethod>b__0方法的函数指针压入栈
IL_0021:newobjinstancevoidclass[mscorlib]System.Func`2<int32,bool>::.ctor(object,
nativeint)//创建一个新的类型Func<Int32,bool>的对象
IL_0026:stsfldclass[mscorlib]System.Func`2<int32,bool>ILDiscovery.Program::'CS$<>9__CachedAnonymousMethodDelegate1'//将上面创建的对象赋值给CS$<>9__CachedAnonymousMethodDelegate1
IL_002b:ldsfldclass[mscorlib]System.Func`2<int32,bool>ILDiscovery.Program::'CS$<>9__CachedAnonymousMethodDelegate1'//将CS$<>9__CachedAnonymousMethodDelegate1的值压入堆栈
IL_0030:callclass[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:callvirtinstanceclass[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.sIL_004c
IL_003f:ldloc.3
IL_0040:callvirtinstance!0class[mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
IL_0045:stloc.2
IL_0046:ldloc.2
IL_0047:callvoid[mscorlib]System.Console::WriteLine(int32)
IL_004c:ldloc.3
IL_004d:callvirtinstancebool[mscorlib]System.Collections.IEnumerator::MoveNext()
IL_0052:brtrue.sIL_003f
IL_0054:leave.sIL_0060
}//end.try
finally
{
IL_0056:ldloc.3
IL_0057:brfalse.sIL_005f
IL_0059:ldloc.3
IL_005a:callvirtinstancevoid[mscorlib]System.IDisposable::Dispose()
IL_005f:endfinally
}//endhandler
IL_0060:ret
}//endofmethodProgram::MyMethod

通过这段IL指令,我们证实了上面的推断:CS$<>9__CachedAnonymousMethodDelegate1变量用来存储<MyMethod>b__0函数本地代码的地址。

另外我们还能得出如下结论:

所谓扩展方法,实际上并没有修改“被扩展的类型”,而是编译器在发现对扩展方法的调用时,自动的将其编译为调用“定义该扩展方法的类型所对应的静态方法”;

所谓Lambda表达式,就像是匿名函数的简化版本,编译器在编译为IL语言时,会生成一个对应的成员函数。

但是,现在通过以上的内容,我们没有看到任何能够说明Linq查询“延迟执行”的东西,因为在第IL_0030句指令返回了一个IEnumerable<>类型的对象后,在接下的指令中,通过获取该对象的枚举器(IL_0037),然后都是正常遍历一个集合对象的代码。

“延迟执行”究竟是如何实现的??

通过Reflector(不得不说,这玩意儿太好用了)

publicoverrideboolMoveNext()
{
if(base.state==1)
{
while(this.index<this.source.Length)
{
TSourcearg=this.source[this.index];
this.index++;
if(this.predicate(arg))
{
base.current=arg;
returntrue;
}
}
this.Dispose();
}
returnfalse;
}

这段代码就是以上MoveNext函数的具体实现,程序实际上是在每次执行MoveNext的时候,才调用this.predicate(arg)函数,而我们之前已经将条件判定函数<MyMethod>b__0指针同过Where方法传给了返回的IEnumerable<int>对象。

这就是Linq“延迟执行的奥秘”——在实际遍历的时候才执行条件判定函数
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: