腾讯理财是什么样的做网站,建立公司网站时什么是重要的,wordpress 负载能力,建设音乐主题网站现代程序员写代码没有人敢说自己没用过泛型#xff0c;这个泛型模板T可以被任何你想要的类型替代#xff0c;确实很魔法很神奇#xff0c;很多人也习以为常了#xff0c;但就是这么有趣的泛型T底层到底是怎么帮你实现的#xff0c;不知道有多少人清楚底层玩法#xff0c;… 现代程序员写代码没有人敢说自己没用过泛型这个泛型模板T可以被任何你想要的类型替代确实很魔法很神奇很多人也习以为常了但就是这么有趣的泛型T底层到底是怎么帮你实现的不知道有多少人清楚底层玩法这篇我就试着来分享一下不一定全对哈。。。一没有泛型前现在的netcore 3.1和最新的.netframework8早已经没有当初那个被人诟病的ArrayList了但很巧这玩意不得不说因为它决定了C#团队痛改前非抛弃过往重新上路上一段ArrayList案例代码。 public class ArrayList{private object[] items;private int index 0;public ArrayList(){items new object[10];}public void Add(object item){items[index] item;}}
上面这段代码为了保证在Add中可以塞入各种类型 eg: int,double,class 就想到了一个绝招用祖宗类object接收这就引入了两大问题装箱拆箱和类型安全。1. 装箱拆箱这个很好理解因为你使用了祖宗类所以当你 Add 的时候塞入的是值类型的话自然就有装箱操作,比如下面代码 ArrayList arrayList new ArrayList();arrayList.Add(3);
1 占用更大的空间这个问题我准备用windbg看一下相信大家知道一个int类型占用4个字节那装箱到堆上是几个字节呢好奇吧????。原始代码和IL代码如下 public static void Main(string[] args){var num 10;var obj (object)num;Console.Read();}IL_0000: nopIL_0001: ldc.i4.s 10IL_0003: stloc.0IL_0004: ldloc.0IL_0005: box [mscorlib]System.Int32IL_000a: stloc.1IL_000b: call int32 [mscorlib]System.Console::Read()IL_0010: popIL_0011: ret
可以清楚的看到IL_0005 中有一个box指令装箱没有问题然后抓一下dump文件。~0s - !clrstack -l - !do 0x0000018300002d48
0:000 ~0s
ntdll!ZwReadFile0x14:
00007ff9fc7baa64 c3 ret
0:000 !clrstack -l
OS Thread Id: 0xfc (0)Child SP IP Call Site
0000002c397fedf0 00007ff985c808f3 ConsoleApp2.Program.Main(System.String[]) [C:\dream\Csharp\ConsoleApp1\ConsoleApp2\Program.cs 28]LOCALS:0x0000002c397fee2c 0x000000000000000a0x0000002c397fee20 0x0000018300002d480000002c397ff038 00007ff9e51b6c93 [GCFrame: 0000002c397ff038]
0:000 !do 0x0000018300002d48
Name: System.Int32
MethodTable: 00007ff9e33285a0
EEClass: 00007ff9e34958a8
Size: 24(0x18) bytes
File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:MT Field Offset Type VT Attr Value Name
00007ff9e33285a0 40005a0 8 System.Int32 1 instance 10 m_value
倒数第5行 Size: 24(0x18) bytes, 可以清楚的看到是24字节。为什么是24个字节8(同步块指针) 8(方法表指针) 4(对象大小)20,但因为是x64位内存是按8对齐也就是要按8的倍数计算所以占用是 888 24 字节原来只有4字节的大小因为装箱已被爆到24字节如果是10000个值类型的装箱那空间占用是不是挺可怕的2 栈到堆的装箱搬运到运输到售后到无害化处理都需要付出重大的人力和机器成本2. 类型不安全很简单因为是祖宗类型object所以无法避免程序员使用乱七八糟的类型,当然这可能是无意的但是编译器确无法规避代码如下ArrayList arrayList new ArrayList();arrayList.Add(3);arrayList.Add(new Actionint((num) { }));arrayList.Add(new object());
面对这两大尴尬的问题C#团队决定重新设计一个类型实现一定终身这就有了泛型。二泛型的出现1. 救世主首先可以明确的说泛型就是为了解决这两个问题而生的你可以在底层提供的ListT中使用Listint,Listdouble。。。等等你看得上的类型而这种技术的底层实现原理才是本篇关注的重点。public static void Main(string[] args){Listdouble list1 new Listdouble();Liststring list3 new Liststring();...}
三泛型原理探究这个问题的探索其实就是 ListT - Listint在何处实现了 T - int 的替换反观java它的泛型实现其实在底层还是用object来替换的C#肯定不是这么做的不然也没这篇文章啦要知道在哪个阶段被替换了你起码要知道C#代码编译的几个阶段为了理解方便我画一张图吧。流程大家也看到了要么在MSIL中被替换要么在JIT编译中被替换。。。public static void Main(string[] args){Listdouble list1 new Listdouble();Listint list2 new Listint();Liststring list3 new Liststring();Listint[] list4 new Listint[]();Console.ReadLine();}
1. 在第一阶段探究因为第一阶段是MSIL代码所以用ILSpy看一下中间代码即可。 IL_0000: nopIL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List1float64::.ctor()IL_0006: stloc.0IL_0007: newobj instance void class [mscorlib]System.Collections.Generic.List1int32::.ctor()IL_000c: stloc.1IL_000d: newobj instance void class [mscorlib]System.Collections.Generic.List1string::.ctor()IL_0012: stloc.2IL_0013: newobj instance void class [mscorlib]System.Collections.Generic.List1int32[]::.ctor()IL_0018: stloc.3IL_0019: call string [mscorlib]System.Console::ReadLine()IL_001e: popIL_001f: ret.class public auto ansi serializable beforefieldinit System.Collections.Generic.List1Textends System.Objectimplements class System.Collections.Generic.IList1!T,class System.Collections.Generic.ICollection1!T,class System.Collections.Generic.IEnumerable1!T,System.Collections.IEnumerable,System.Collections.IList,System.Collections.ICollection,class System.Collections.Generic.IReadOnlyList1!T,class System.Collections.Generic.IReadOnlyCollection1!T
从上面的IL代码中可以看到最终的类定义还是 System.Collections.Generic.List1\T说明在中间代码阶段还是没有实现 T - int 的替换。2. 在第二阶段探究想看到JIT编译后的代码这个说难也不难其实每个对象头上都有一个方法表指针而这个指针指向的就是方法表方法表中有该类型的所有最终生成方法如果不好理解我就画个图。!dumpheap -stat 寻找托管堆上的四个List对象。
0:000 !dumpheap -stat
Statistics:MT Count TotalSize Class Name
00007ff9e3314320 1 32 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle
00007ff9e339b4b8 1 40 System.Collections.Generic.List1[[System.Double, mscorlib]]
00007ff9e333a068 1 40 System.Collections.Generic.List1[[System.Int32, mscorlib]]
00007ff9e3330d58 1 40 System.Collections.Generic.List1[[System.String, mscorlib]]
00007ff9e3314a58 1 40 System.IO.StreamNullStream
00007ff9e3314510 1 40 Microsoft.Win32.Win32NativeInputRecord
00007ff9e3314218 1 40 System.Text.InternalEncoderBestFitFallback
00007ff985b442c0 1 40 System.Collections.Generic.List1[[System.Int32[], mscorlib]]
00007ff9e338fd28 1 48 System.Text.DBCSCodePageEncodingDBCSDecoder
00007ff9e3325ef0 1 48 System.SharedStatics
可以看到从托管堆中找到了4个list对象现在我就挑一个最简单的 System.Collections.Generic.List1[[System.Int32, mscorlib]] 前面的 00007ff9e333a068 就是方法表地址。!dumpmt -md 00007ff9e333a068
0:000 !dumpmt -md 00007ff9e333a068
EEClass: 00007ff9e349b008
Module: 00007ff9e3301000
Name: System.Collections.Generic.List1[[System.Int32, mscorlib]]
mdToken: 00000000020004af
File: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
BaseSize: 0x28
ComponentSize: 0x0
Slots in VTable: 77
Number of IFaces in IFaceMap: 8
--------------------------------------
MethodDesc TableEntry MethodDesc JIT Name
00007ff9e3882450 00007ff9e3308de8 PreJIT System.Object.ToString()
00007ff9e389cc60 00007ff9e34cb9b0 PreJIT System.Object.Equals(System.Object)
00007ff9e3882090 00007ff9e34cb9d8 PreJIT System.Object.GetHashCode()
00007ff9e387f420 00007ff9e34cb9e0 PreJIT System.Object.Finalize()
00007ff9e38a3650 00007ff9e34dc6e8 PreJIT System.Collections.Generic.List1[[System.Int32, mscorlib]].Add(Int32)
00007ff9e4202dc0 00007ff9e34dc7f8 PreJIT System.Collections.Generic.List1[[System.Int32, mscorlib]].Insert(Int32, Int32)
上面方法表中的方法过多我做了一下删减可以清楚的看到此时Add方法已经接受(Int32)类型的数据了说明在JIT编译之后终于实现了 T - int 的替换然后再把 Listdouble 打出来看一下。
0:000 !dumpmt -md 00007ff9e339b4b8
MethodDesc TableEntry MethodDesc JIT Name
00007ff9e3882450 00007ff9e3308de8 PreJIT System.Object.ToString()
00007ff9e389cc60 00007ff9e34cb9b0 PreJIT System.Object.Equals(System.Object)
00007ff9e3882090 00007ff9e34cb9d8 PreJIT System.Object.GetHashCode()
00007ff9e387f420 00007ff9e34cb9e0 PreJIT System.Object.Finalize()
00007ff9e4428730 00007ff9e34e4170 PreJIT System.Collections.Generic.List1[[System.Double, mscorlib]].Add(Double)
00007ff9e3867a00 00007ff9e34e4280 PreJIT System.Collections.Generic.List1[[System.Double, mscorlib]].Insert(Int32, Double)
上面看的都是值类型接下来再看一下如果 T 是引用类型会是怎么样呢
0:000 !dumpmt -md 00007ff9e3330d58
MethodDesc TableEntry MethodDesc JIT Name
00007ff9e3890060 00007ff9e34eb058 PreJIT System.Collections.Generic.List1[[System.__Canon, mscorlib]].Add(System.__Canon)0:000 !dumpmt -md 00007ff985b442c0
MethodDesc TableEntry MethodDesc JIT Name
00007ff9e3890060 00007ff9e34eb058 PreJIT System.Collections.Generic.List1[[System.__Canon, mscorlib]].Add(System.__Canon)
可以看到当是Listint[] 和 Liststring 的时候JIT使用了 System.__Canon 这么一个类型作为替代有可能人家是摄影爱好者吧为什么用__Canon替代引用类型这是因为它想让能共享代码区域的方法都共享来节省空间和内存吧不信的话可以看看它们的Entry列都是同一个内存地址00007ff9e3890060, 打印出来就是这么一段汇编。
0:000 !u 00007ff9e3890060
preJIT generated code
System.Collections.Generic.List1[[System.__Canon, mscorlib]].Add(System.__Canon)
Begin 00007ff9e3890060, size 4a00007ff9e3890060 57 push rdi
00007ff9e3890061 56 push rsi
00007ff9e3890062 4883ec28 sub rsp,28h
00007ff9e3890066 488bf1 mov rsi,rcx
00007ff9e3890069 488bfa mov rdi,rdx
00007ff9e389006c 8b4e18 mov ecx,dword ptr [rsi18h]
00007ff9e389006f 488b5608 mov rdx,qword ptr [rsi8]
00007ff9e3890073 3b4a08 cmp ecx,dword ptr [rdx8]
00007ff9e3890076 7422 je mscorlib_ni0x59009a (00007ff9e389009a)
00007ff9e3890078 488b4e08 mov rcx,qword ptr [rsi8]
00007ff9e389007c 8b5618 mov edx,dword ptr [rsi18h]
00007ff9e389007f 448d4201 lea r8d,[rdx1]
00007ff9e3890083 44894618 mov dword ptr [rsi18h],r8d
00007ff9e3890087 4c8bc7 mov r8,rdi
00007ff9e389008a ff152088faff call qword ptr [mscorlib_ni0x5388b0 (00007ff9e38388b0)] (JitHelp: CORINFO_HELP_ARRADDR_ST)
00007ff9e3890090 ff461c inc dword ptr [rsi1Ch]
00007ff9e3890093 4883c428 add rsp,28h
00007ff9e3890097 5e pop rsi
00007ff9e3890098 5f pop rdi
00007ff9e3890099 c3 ret
00007ff9e389009a 8b5618 mov edx,dword ptr [rsi18h]
00007ff9e389009d ffc2 inc edx
00007ff9e389009f 488bce mov rcx,rsi
00007ff9e38900a2 90 nop
00007ff9e38900a3 e8c877feff call mscorlib_ni0x577870 (00007ff9e3877870) (System.Collections.Generic.List1[[System.__Canon, mscorlib]].EnsureCapacity(Int32), mdToken: 00000000060039e5)
00007ff9e38900a8 ebce jmp mscorlib_ni0x590078 (00007ff9e3890078)
然后再回过头看Listint 和 Listdouble ,从Entry列中看确实不是一个地址说明Listint 和 Listdouble 是两个完全不一样的Add方法看得懂汇编的可以自己看一下哈。。。MethodDesc TableEntry MethodDesc JIT Name
00007ff9e38a3650 00007ff9e34dc6e8 PreJIT System.Collections.Generic.List1[[System.Int32, mscorlib]].Add(Int32)
00007ff9e4428730 00007ff9e34e4170 PreJIT System.Collections.Generic.List1[[System.Double, mscorlib]].Add(Double)0:000 !u 00007ff9e38a3650
preJIT generated code
System.Collections.Generic.List1[[System.Int32, mscorlib]].Add(Int32)
Begin 00007ff9e38a3650, size 5000007ff9e38a3650 57 push rdi
00007ff9e38a3651 56 push rsi
00007ff9e38a3652 4883ec28 sub rsp,28h
00007ff9e38a3656 488bf1 mov rsi,rcx
00007ff9e38a3659 8bfa mov edi,edx
00007ff9e38a365b 8b5618 mov edx,dword ptr [rsi18h]
00007ff9e38a365e 488b4e08 mov rcx,qword ptr [rsi8]
00007ff9e38a3662 3b5108 cmp edx,dword ptr [rcx8]
00007ff9e38a3665 7423 je mscorlib_ni0x5a368a (00007ff9e38a368a)
00007ff9e38a3667 488b5608 mov rdx,qword ptr [rsi8]
00007ff9e38a366b 8b4e18 mov ecx,dword ptr [rsi18h]
00007ff9e38a366e 8d4101 lea eax,[rcx1]
00007ff9e38a3671 894618 mov dword ptr [rsi18h],eax
00007ff9e38a3674 3b4a08 cmp ecx,dword ptr [rdx8]
00007ff9e38a3677 7321 jae mscorlib_ni0x5a369a (00007ff9e38a369a)
00007ff9e38a3679 4863c9 movsxd rcx,ecx
00007ff9e38a367c 897c8a10 mov dword ptr [rdxrcx*410h],edi
00007ff9e38a3680 ff461c inc dword ptr [rsi1Ch]
00007ff9e38a3683 4883c428 add rsp,28h
00007ff9e38a3687 5e pop rsi
00007ff9e38a3688 5f pop rdi
00007ff9e38a3689 c3 ret
00007ff9e38a368a 8b5618 mov edx,dword ptr [rsi18h]
00007ff9e38a368d ffc2 inc edx
00007ff9e38a368f 488bce mov rcx,rsi
00007ff9e38a3692 90 nop
00007ff9e38a3693 e8a8e60700 call mscorlib_ni0x621d40 (00007ff9e3921d40) (System.Collections.Generic.List1[[System.Int32, mscorlib]].EnsureCapacity(Int32), mdToken: 00000000060039e5)
00007ff9e38a3698 ebcd jmp mscorlib_ni0x5a3667 (00007ff9e38a3667)
00007ff9e38a369a e8bf60f9ff call mscorlib_ni0x53975e (00007ff9e383975e) (mscorlib_ni)
00007ff9e38a369f cc int 30:000 !u 00007ff9e4428730
preJIT generated code
System.Collections.Generic.List1[[System.Double, mscorlib]].Add(Double)
Begin 00007ff9e4428730, size 5a00007ff9e4428730 56 push rsi
00007ff9e4428731 4883ec20 sub rsp,20h
00007ff9e4428735 488bf1 mov rsi,rcx
00007ff9e4428738 8b5618 mov edx,dword ptr [rsi18h]
00007ff9e442873b 488b4e08 mov rcx,qword ptr [rsi8]
00007ff9e442873f 3b5108 cmp edx,dword ptr [rcx8]
00007ff9e4428742 7424 je mscorlib_ni0x1128768 (00007ff9e4428768)
00007ff9e4428744 488b5608 mov rdx,qword ptr [rsi8]
00007ff9e4428748 8b4e18 mov ecx,dword ptr [rsi18h]
00007ff9e442874b 8d4101 lea eax,[rcx1]
00007ff9e442874e 894618 mov dword ptr [rsi18h],eax
00007ff9e4428751 3b4a08 cmp ecx,dword ptr [rdx8]
00007ff9e4428754 732e jae mscorlib_ni0x1128784 (00007ff9e4428784)
00007ff9e4428756 4863c9 movsxd rcx,ecx
00007ff9e4428759 f20f114cca10 movsd mmword ptr [rdxrcx*810h],xmm1
00007ff9e442875f ff461c inc dword ptr [rsi1Ch]
00007ff9e4428762 4883c420 add rsp,20h
00007ff9e4428766 5e pop rsi
00007ff9e4428767 c3 ret
00007ff9e4428768 f20f114c2438 movsd mmword ptr [rsp38h],xmm1
00007ff9e442876e 8b5618 mov edx,dword ptr [rsi18h]
00007ff9e4428771 ffc2 inc edx
00007ff9e4428773 488bce mov rcx,rsi
00007ff9e4428776 90 nop
00007ff9e4428777 e854fbffff call mscorlib_ni0x11282d0 (00007ff9e44282d0) (System.Collections.Generic.List1[[System.Double, mscorlib]].EnsureCapacity(Int32), mdToken: 00000000060039e5)
00007ff9e442877c f20f104c2438 movsd xmm1,mmword ptr [rsp38h]
00007ff9e4428782 ebc0 jmp mscorlib_ni0x1128744 (00007ff9e4428744)
00007ff9e4428784 e8d50f41ff call mscorlib_ni0x53975e (00007ff9e383975e) (mscorlib_ni)
00007ff9e4428789 cc int 3
可能你有点蒙我画一张图吧。四总结泛型T真正的被代替是在 JIT编译时才实现的四个ListT 会生成四个具有相应具体类型的类对象所以就不存在拆箱和装箱的问题而类型的限定visualstudio编译器工具提前就帮我们约束好啦。夜深了先休息啦希望本篇对你有帮助。