技术开发 频道

CLR系列:浅析泛型的本质

  我们知道一个程序域有个loader heap,里面有个method table,每种类型都有自己的一个一个方法表,而且这个方法表列出所有的方法,方法表中包含了一个槽表,指向各个方法的描述MethodDesc),提供了类型的行为能力,用于不同的交互操作实现的调用,在第一次调用时,会调用JIT编译程序,对其进行扫描,发现有引用别的类型,就会导向元数据,让后再根据元数据导向到所要查找的类型的具体位置,当范型参数是引用类型的时候,引用类型变量都是指向托管堆的指针,(在32位的操作系统上指针都是32位的)而对于指针完全是可以用相同的方式操作的。这再很大程度上避免了代码爆炸。如果为值类型的时候,由于值类型都是直接操作数据本身,JIT不会为一个不知道大小的参数去产生同样的代码。下次JIT编译的时候,先去查找是否有相同的类型代码,如果有的话,就不会再次编译。因此C#泛型做到了时间和空间的双重效应。看看下面windbg+sos的调试,我们看看它经过JIT编译过后的本地代码,我们更加肯定的验证了以上的我们所论述的。

0:010> !u 00dc1128
Normal JIT generated code
Collections.Program.Main(System.String[])
Begin 00dc1128, size
90
00dc117f 8bdf            mov     ebx,edi
00dc1181 8bce            mov     ecx,esi
00dc1183
3909            cmp     dword ptr [ecx],ecx
00dc1185 e886b4cbff      call    00a7c610 (Collections.Generics_CSharp.intrGeneric[[System.Int32, mscorlib]](), mdToken:
06000003)
00dc118a
90              nop
00dc118b 8bce            mov     ecx,esi
00dc118d ba2892a700      mov     edx,0A79228h (MD: Collections.Generics_CSharp.intrGeneric[[System.Object, mscorlib]]())
00dc1192
3909            cmp     dword ptr [ecx],ecx
00dc1194 e897b4cbff      call    00a7c630 (Collections.Generics_CSharp.intrGeneric[[System.__Canon, mscorlib]](), mdToken:
06000003)
00dc1199
90              nop
00dc119a 8bce            mov     ecx,esi
00dc119c ba6892a700      mov     edx,0A79268h (MD: Collections.Generics_CSharp.intrGeneric[[System.String, mscorlib]]())
00dc11a1
3909            cmp     dword ptr [ecx],ecx
00dc11a3 e888b4cbff      call    00a7c630 (Collections.Generics_CSharp.intrGeneric[[System.__Canon, mscorlib]](), mdToken:
06000003)
00dc11a8
90              nop
00dc11a9 e81e886278      call    mscorlib_ni
+0x3299cc (793e99cc) (System.Console.Read(), mdToken: 060007b6)
00dc11ae
90              nop
00dc11af
90              nop
00dc11b0 8d65f4          lea     esp,[ebp
-0Ch]
00dc11b3 5b              pop     ebx
00dc11b4 5e              pop     esi
00dc11b5 5f              pop     edi
00dc11b6 5d              pop     ebp
00dc11b7 c3              ret

  这里调试的正式调用泛型方法的函数,看看红色的地方,看看这三个CALL,大家可以看到第一个(int)的地址是:00a7c610 ,后面两个(object和string)的地址都是:00a7c630 。这可以说明值类型是不同的类型生成不同的代码,而引用类型是共用一个代码。大家还可以看到mdToken: 06000003。这也说明无论是值类型还是引用类型都是共用一个占位符,引用的是同一个类型下的方法和同一个MethodDesc,进而验证泛型是基于JIT的。

  由此我们可以看出为了是泛型能够工作,MS必须做一下的工作:

 创建新的IL指令,使之能够识别类型参数。

 修改现有的元数据表的格式。

 修改各种编程语言使他们能支持泛型。

 修改编译器,能生成新的IL和元数据。

 修改JIT编译器。

  以上是本人对C#泛型的一点理解。。关于泛型的特性和语法不是本文的重点,园子里有很多文章可以参考。有不同意见欢迎大家指出来。希望大家看了本文后对泛型有个更加深入的理解。

 

0
相关文章