用了这么多年的泛型,你对它到底有多了解?
现代程序员写代码没有人敢说自己没用过泛型,这个泛型模板T可以被任何你想要的类型替代,确实很魔法很神奇,很多人也习以为常了,但就是这么有趣的泛型T底层到底是怎么帮你实现的,不知道有多少人清楚底层玩法,这篇我就试着来分享一下,不一定全对哈一:没有泛型前
现在的netcore3。1和最新的。netframework8早已经没有当初那个被人诟病的ArrayList了,但很巧这玩意不得不说,因为它决定了C团队痛改前非,抛弃过往重新上路,上一段ArrayList案例代码。publicclassArrayList{privateobject〔〕items;privateintindex0;publicArrayList(){itemsnewobject〔10〕;}publicvoidAdd(objectitem){items〔index〕item;}}
上面这段代码,为了保证在Add中可以塞入各种类型eg:int,double,class,就想到了一个绝招用祖宗类object接收,这就引入了两大问题,装箱拆箱和类型安全。1。装箱拆箱
这个很好理解,因为你使用了祖宗类,所以当你Add的时候塞入的是值类型的话,自然就有装箱操作,比如下面代码:ArrayListarrayListnewArrayList();arrayList。Add(3);占用更大的空间
这个问题我准备用windbg看一下,相信大家知道一个int类型占用4个字节,那装箱到堆上是几个字节呢,好奇吧。
原始代码和IL代码如下:publicstaticvoidMain(string〔〕args){varnum10;varobj(object)num;Console。Read();}IL0000:nopIL0001:ldc。i4。s10IL0003:stloc。0IL0004:ldloc。0IL0005:box〔mscorlib〕System。Int32IL000a:stloc。1IL000b:callint32〔mscorlib〕System。Console::Read()IL0010:popIL0011:ret
可以清楚的看到IL0005中有一个box指令,装箱没有问题,然后抓一下dump文件。
0s!clrstackl!do0x0000018300002d480:0000sntdll!ZwReadFile0x14:00007ff9fc7baa64c3ret0:000!clrstacklOSThreadId:0xfc(0)ChildSPIPCallSite0000002c397fedf000007ff985c808f3ConsoleApp2。Program。Main(System。String〔〕)〔C:dreamCsharpConsoleApp1ConsoleApp2Program。cs28〕LOCALS:0x0000002c397fee2c0x000000000000000a0x0000002c397fee200x0000018300002d480000002c397ff03800007ff9e51b6c93〔GCFrame:0000002c397ff038〕0:000!do0x0000018300002d48Name:System。Int32MethodTable:00007ff9e33285a0EEClass:00007ff9e34958a8Size:24(0x18)bytesFile:C:WINDOWSMicrosoft。NetassemblyGAC64mscorlibv4。04。0。0。0b77a5c561934e089mscorlib。dllFields:MTFieldOffsetTypeVTAttrValueName00007ff9e33285a040005a08System。Int321instance10mvalue
倒数第5行Size:24(0x18)bytes,可以清楚的看到是24字节。为什么是24个字节,8(同步块指针)8(方法表指针)4(对象大小)20,但因为是x64位,内存是按8对齐,也就是要按8的倍数计算,所以占用是88824字节,原来只有4字节的大小因为装箱已被爆到24字节,如果是10000个值类型的装箱那空间占用是不是挺可怕的?栈到堆的装箱搬运到运输到售后到无害化处理都需要付出重大的人力和机器成本2。类型不安全
很简单,因为是祖宗类型object,所以无法避免程序员使用乱七八糟的类型,当然这可能是无意的,但是编译器确无法规避,代码如下:ArrayListarrayListnewArrayList();arrayList。Add(3);arrayList。Add(newActionint((num){}));arrayList。Add(newobject());
面对这两大尴尬的问题,C团队决定重新设计一个类型,实现一定终身,这就有了泛型。二:泛型的出现1。救世主
首先可以明确的说,泛型就是为了解决这两个问题而生的,你可以在底层提供的List中使用List,List等等你看得上的类型,而这种技术的底层实现原理才是本篇关注的重点。publicstaticvoidMain(string〔〕args){Listdoublelist1newListdouble();Liststringlist3newListstring();。。。}三:泛型原理探究
这个问题的探索其实就是ListList在何处实现了Tint的替换,反观java,它的泛型实现其实在底层还是用object来替换的,C肯定不是这么做的,不然也没这篇文章啦,要知道在哪个阶段被替换了,你起码要知道C代码编译的几个阶段,为了理解方便,我画一张图吧。
流程大家也看到了,要么在MSIL中被替换,要么在JIT编译中被替换publicstaticvoidMain(string〔〕args){Listdoublelist1newListdouble();Listintlist2newListint();Liststringlist3newListstring();Listint〔〕list4newListint〔〕();Console。ReadLine();}1。在第一阶段探究
因为第一阶段是MSIL代码,所以用ILSpy看一下中间代码即可。IL0000:nopIL0001:newobjinstancevoidclass〔mscorlib〕System。Collections。Generic。List1float64::。ctor()IL0006:stloc。0IL0007:newobjinstancevoidclass〔mscorlib〕System。Collections。Generic。List1int32::。ctor()IL000c:stloc。1IL000d:newobjinstancevoidclass〔mscorlib〕System。Collections。Generic。List1string::。ctor()IL0012:stloc。2IL0013:newobjinstancevoidclass〔mscorlib〕System。Collections。Generic。List1int32〔〕::。ctor()IL0018:stloc。3IL0019:callstring〔mscorlib〕System。Console::ReadLine()IL001e:popIL001f:ret。classpublicautoansiserializablebeforefieldinitSystem。Collections。Generic。List1TextendsSystem。ObjectimplementsclassSystem。Collections。Generic。IList1!T,classSystem。Collections。Generic。ICollection1!T,classSystem。Collections。Generic。IEnumerable1!T,System。Collections。IEnumerable,System。Collections。IList,System。Collections。ICollection,classSystem。Collections。Generic。IReadOnlyList1!T,classSystem。Collections。Generic。IReadOnlyCollection1!T
从上面的IL代码中可以看到,最终的类定义还是System。Collections。Generic。List1,说明在中间代码阶段还是没有实现Tint的替换。2。在第二阶段探究
想看到JIT编译后的代码,这个说难也不难,其实每个对象头上都有一个方法表指针,而这个指针指向的就是方法表,方法表中有该类型的所有最终生成方法,如果不好理解,我就画个图。
!dumpheapstat寻找托管堆上的四个List对象。0:000!dumpheapstatStatistics:MTCountTotalSizeClassName00007ff9e3314320132Microsoft。Win32。SafeHandles。SafeViewOfFileHandle00007ff9e339b4b8140System。Collections。Generic。List1〔〔System。Double,mscorlib〕〕00007ff9e333a068140System。Collections。Generic。List1〔〔System。Int32,mscorlib〕〕00007ff9e3330d58140System。Collections。Generic。List1〔〔System。String,mscorlib〕〕00007ff9e3314a58140System。IO。StreamNullStream00007ff9e3314510140Microsoft。Win32。Win32NativeInputRecord00007ff9e3314218140System。Text。InternalEncoderBestFitFallback00007ff985b442c0140System。Collections。Generic。List1〔〔System。Int32〔〕,mscorlib〕〕00007ff9e338fd28148System。Text。DBCSCodePageEncodingDBCSDecoder00007ff9e3325ef0148System。SharedStatics
可以看到从托管堆中找到了4个list对象,现在我就挑一个最简单的System。Collections。Generic。List1〔〔System。Int32,mscorlib〕〕,前面的00007ff9e333a068就是方法表地址。
!dumpmtmd00007ff9e333a0680:000!dumpmtmd00007ff9e333a068EEClass:00007ff9e349b008Module:00007ff9e3301000Name:System。Collections。Generic。List1〔〔System。Int32,mscorlib〕〕mdToken:00000000020004afFile:C:WINDOWSMicrosoft。NetassemblyGAC64mscorlibv4。04。0。0。0b77a5c561934e089mscorlib。dllBaseSize:0x28ComponentSize:0x0SlotsinVTable:77NumberofIFacesinIFaceMap:8MethodDescTableEntryMethodDescJITName00007ff9e388245000007ff9e3308de8PreJITSystem。Object。ToString()00007ff9e389cc6000007ff9e34cb9b0PreJITSystem。Object。Equals(System。Object)00007ff9e388209000007ff9e34cb9d8PreJITSystem。Object。GetHashCode()00007ff9e387f42000007ff9e34cb9e0PreJITSystem。Object。Finalize()00007ff9e38a365000007ff9e34dc6e8PreJITSystem。Collections。Generic。List1〔〔System。Int32,mscorlib〕〕。Add(Int32)00007ff9e4202dc000007ff9e34dc7f8PreJITSystem。Collections。Generic。List1〔〔System。Int32,mscorlib〕〕。Insert(Int32,Int32)
上面方法表中的方法过多,我做了一下删减,可以清楚的看到,此时Add方法已经接受(Int32)类型的数据了,说明在JIT编译之后,终于实现了Tint的替换,然后再把List打出来看一下。0:000!dumpmtmd00007ff9e339b4b8MethodDescTableEntryMethodDescJITName00007ff9e388245000007ff9e3308de8PreJITSystem。Object。ToString()00007ff9e389cc6000007ff9e34cb9b0PreJITSystem。Object。Equals(System。Object)00007ff9e388209000007ff9e34cb9d8PreJITSystem。Object。GetHashCode()00007ff9e387f42000007ff9e34cb9e0PreJITSystem。Object。Finalize()00007ff9e442873000007ff9e34e4170PreJITSystem。Collections。Generic。List1〔〔System。Double,mscorlib〕〕。Add(Double)00007ff9e3867a0000007ff9e34e4280PreJITSystem。Collections。Generic。List1〔〔System。Double,mscorlib〕〕。Insert(Int32,Double)
上面看的都是值类型,接下来再看一下如果T是引用类型会是怎么样呢?0:000!dumpmtmd00007ff9e3330d58MethodDescTableEntryMethodDescJITName00007ff9e389006000007ff9e34eb058PreJITSystem。Collections。Generic。List1〔〔System。Canon,mscorlib〕〕。Add(System。Canon)0:000!dumpmtmd00007ff985b442c0MethodDescTableEntryMethodDescJITName00007ff9e389006000007ff9e34eb058PreJITSystem。Collections。Generic。List1〔〔System。Canon,mscorlib〕〕。Add(System。Canon)
可以看到当是Listint〔〕和List的时候,JIT使用了System。Canon这么一个类型作为替代,有可能人家是摄影爱好者吧,为什么用Canon替代引用类型,这是因为它想让能共享代码区域的方法都共享来节省空间和内存吧,不信的话可以看看它们的Entry列都是同一个内存地址:00007ff9e3890060,打印出来就是这么一段汇编。0:000!u00007ff9e3890060preJITgeneratedcodeSystem。Collections。Generic。List1〔〔System。Canon,mscorlib〕〕。Add(System。Canon)Begin00007ff9e3890060,size4a00007ff9e389006057pushrdi00007ff9e389006156pushrsi00007ff9e38900624883ec28subrsp,28h00007ff9e3890066488bf1movrsi,rcx00007ff9e3890069488bfamovrdi,rdx00007ff9e389006c8b4e18movecx,dwordptr〔rsi18h〕00007ff9e389006f488b5608movrdx,qwordptr〔rsi8〕00007ff9e38900733b4a08cmpecx,dwordptr〔rdx8〕00007ff9e38900767422jemscorlibni0x59009a(00007ff9e389009a)00007ff9e3890078488b4e08movrcx,qwordptr〔rsi8〕00007ff9e389007c8b5618movedx,dwordptr〔rsi18h〕00007ff9e389007f448d4201lear8d,〔rdx1〕00007ff9e389008344894618movdwordptr〔rsi18h〕,r8d00007ff9e38900874c8bc7movr8,rdi00007ff9e389008aff152088faffcallqwordptr〔mscorlibni0x5388b0(00007ff9e38388b0)〕(JitHelp:CORINFOHELPARRADDRST)00007ff9e3890090ff461cincdwordptr〔rsi1Ch〕00007ff9e38900934883c428addrsp,28h00007ff9e38900975epoprsi00007ff9e38900985fpoprdi00007ff9e3890099c3ret00007ff9e389009a8b5618movedx,dwordptr〔rsi18h〕00007ff9e389009dffc2incedx00007ff9e389009f488bcemovrcx,rsi00007ff9e38900a290nop00007ff9e38900a3e8c877feffcallmscorlibni0x577870(00007ff9e3877870)(System。Collections。Generic。List1〔〔System。Canon,mscorlib〕〕。EnsureCapacity(Int32),mdToken:00000000060039e5)00007ff9e38900a8ebcejmpmscorlibni0x590078(00007ff9e3890078)
然后再回过头看List和List,从Entry列中看确实不是一个地址,说明List和List是两个完全不一样的Add方法,看得懂汇编的可以自己看一下哈MethodDescTableEntryMethodDescJITName00007ff9e38a365000007ff9e34dc6e8PreJITSystem。Collections。Generic。List1〔〔System。Int32,mscorlib〕〕。Add(Int32)00007ff9e442873000007ff9e34e4170PreJITSystem。Collections。Generic。List1〔〔System。Double,mscorlib〕〕。Add(Double)0:000!u00007ff9e38a3650preJITgeneratedcodeSystem。Collections。Generic。List1〔〔System。Int32,mscorlib〕〕。Add(Int32)Begin00007ff9e38a3650,size5000007ff9e38a365057pushrdi00007ff9e38a365156pushrsi00007ff9e38a36524883ec28subrsp,28h00007ff9e38a3656488bf1movrsi,rcx00007ff9e38a36598bfamovedi,edx00007ff9e38a365b8b5618movedx,dwordptr〔rsi18h〕00007ff9e38a365e488b4e08movrcx,qwordptr〔rsi8〕00007ff9e38a36623b5108cmpedx,dwordptr〔rcx8〕00007ff9e38a36657423jemscorlibni0x5a368a(00007ff9e38a368a)00007ff9e38a3667488b5608movrdx,qwordptr〔rsi8〕00007ff9e38a366b8b4e18movecx,dwordptr〔rsi18h〕00007ff9e38a366e8d4101leaeax,〔rcx1〕00007ff9e38a3671894618movdwordptr〔rsi18h〕,eax00007ff9e38a36743b4a08cmpecx,dwordptr〔rdx8〕00007ff9e38a36777321jaemscorlibni0x5a369a(00007ff9e38a369a)00007ff9e38a36794863c9movsxdrcx,ecx00007ff9e38a367c897c8a10movdwordptr〔rdxrcx410h〕,edi00007ff9e38a3680ff461cincdwordptr〔rsi1Ch〕00007ff9e38a36834883c428addrsp,28h00007ff9e38a36875epoprsi00007ff9e38a36885fpoprdi00007ff9e38a3689c3ret00007ff9e38a368a8b5618movedx,dwordptr〔rsi18h〕00007ff9e38a368dffc2incedx00007ff9e38a368f488bcemovrcx,rsi00007ff9e38a369290nop00007ff9e38a3693e8a8e60700callmscorlibni0x621d40(00007ff9e3921d40)(System。Collections。Generic。List1〔〔System。Int32,mscorlib〕〕。EnsureCapacity(Int32),mdToken:00000000060039e5)00007ff9e38a3698ebcdjmpmscorlibni0x5a3667(00007ff9e38a3667)00007ff9e38a369ae8bf60f9ffcallmscorlibni0x53975e(00007ff9e383975e)(mscorlibni)00007ff9e38a369fccint30:000!u00007ff9e4428730preJITgeneratedcodeSystem。Collections。Generic。List1〔〔System。Double,mscorlib〕〕。Add(Double)Begin00007ff9e4428730,size5a00007ff9e442873056pushrsi00007ff9e44287314883ec20subrsp,20h00007ff9e4428735488bf1movrsi,rcx00007ff9e44287388b5618movedx,dwordptr〔rsi18h〕00007ff9e442873b488b4e08movrcx,qwordptr〔rsi8〕00007ff9e442873f3b5108cmpedx,dwordptr〔rcx8〕00007ff9e44287427424jemscorlibni0x1128768(00007ff9e4428768)00007ff9e4428744488b5608movrdx,qwordptr〔rsi8〕00007ff9e44287488b4e18movecx,dwordptr〔rsi18h〕00007ff9e442874b8d4101leaeax,〔rcx1〕00007ff9e442874e894618movdwordptr〔rsi18h〕,eax00007ff9e44287513b4a08cmpecx,dwordptr〔rdx8〕00007ff9e4428754732ejaemscorlibni0x1128784(00007ff9e4428784)00007ff9e44287564863c9movsxdrcx,ecx00007ff9e4428759f20f114cca10movsdmmwordptr〔rdxrcx810h〕,xmm100007ff9e442875fff461cincdwordptr〔rsi1Ch〕00007ff9e44287624883c420addrsp,20h00007ff9e44287665epoprsi00007ff9e4428767c3ret00007ff9e4428768f20f114c2438movsdmmwordptr〔rsp38h〕,xmm100007ff9e442876e8b5618movedx,dwordptr〔rsi18h〕00007ff9e4428771ffc2incedx00007ff9e4428773488bcemovrcx,rsi00007ff9e442877690nop00007ff9e4428777e854fbffffcallmscorlibni0x11282d0(00007ff9e44282d0)(System。Collections。Generic。List1〔〔System。Double,mscorlib〕〕。EnsureCapacity(Int32),mdToken:00000000060039e5)00007ff9e442877cf20f104c2438movsdxmm1,mmwordptr〔rsp38h〕00007ff9e4428782ebc0jmpmscorlibni0x1128744(00007ff9e4428744)00007ff9e4428784e8d50f41ffcallmscorlibni0x53975e(00007ff9e383975e)(mscorlibni)00007ff9e4428789ccint3
可能你有点蒙,我画一张图吧。
四:总结
泛型T真正的被代替是在JIT编译时才实现的,四个List会生成四个具有相应具体类型的类对象,所以就不存在拆箱和装箱的问题,而类型的限定visualstudio编译器工具提前就帮我们约束好啦。
送你一朵小红花癌症患者不为人知的一面大家好,我是老李。今天和大家聊聊最新上映的电影《送你一朵小红花》。前天刚在电影院看了易烊千玺和新一代谋女郎主演的新电影,送你一朵小红花。影片主要讲述的是两个抗……
买车面子很重要吗?为什么身边都推荐买BBA?想想挺有意思买车面子重要吗?问过身边朋友,有的说重要,有的说不重要。说重要的理由是能给车主带来正向收益和加成,说不重要的理由是买车选合适的就行,不要花了自己钱买别人喜欢的车。在这个问题上,……
在碳中和这条路上我国究竟如何弯道超车?大家好,我是老李。今天和大家再来聊聊碳中和。昨天的文章《碳中和为什么对我国发展如此重要?》发布之后,有朋友私信我说聊的不是很深入,那么今天我们就深入来聊一下。……
小米弹力鞋别出心裁!爆米花底反光设计,远照1000mm时隔五个月开学,宿舍里的环境相信大家都心中有数,蟑螂老鼠都成家了,我还是一个人。最可恶的是,鞋子全都发霉了,逼于无奈只好都买新的了。我的鞋子从不看品牌,舒适就好,其中米家运动鞋……
苹果手机便签怎么朗读文本文字?很多人在使用苹果手机的时候,都会用到便签软件,使用便签软件可以非常方便的帮助自己记录各种待办事项和计划安排。有的时候,在不方便查看手机便签内容的时候,可以使用苹果手机的朗读功能……
以后看片都会有中文字幕了平常大家无聊的时候怎么办?大多数人应该是玩玩游戏,追追剧吧。。。。。那你是否经历过观看外语电影时却无字幕的情况?随着文化的多维度发展,国外不少优秀的影视剧被国……
100万新能源车下线比亚迪挺进欧洲私人市场5月19日,比亚迪第100万辆新能源汽车汉EV在深圳坪山工厂下线。政府行业主管部门、行业协会的有关领导,比亚迪的供应商伙伴代表、400家媒体及比亚迪近百位车主代表在仪式现场,一……
决战合资SUV星越L开启吉利新时代7月20日,吉利2021年度车型星越L迎来高光时刻。中国星、全新品牌价值主张,以及许久未曾为新车站台的书福哥现身发布会现场,都让吉利此次发布会显得不一般。以星越L为……
建邦基金与两县政府合作组建防贫基金和乡村振兴基金2020年5月,大名县扶贫办正式聘用建邦基金为防贫产业基金的基金管理人,建邦基金开创了全省首例与政府扶贫办共同出资成立的防贫产业及乡村振兴基金。该基金主要投资于邯郸市大名县可带……
何以逆势上扬,突破的创新者才有机会2020年1月,新能源汽车的销量可以说大幅滑坡。据中国汽车业协会统计显示,新能源车产销分别为4。04。4万辆,分别同比55。4、54。4,环比52。38、73。01。其中,新能……
比亚迪新能源汽车远销国内外。日前,我们从比亚迪官方了解到,将有450台比亚迪唐(参数询价)EV车型在广州港集结,启程前往挪威。至此,比亚迪已累计向挪威发运超1000台唐EV。今年5月,比亚迪对外宣布……
如何为web3社区设计有效的社区激励系统?今天看了一篇a16z的研究文章,里面提到了如何为web3项目设计有效的社区激励,以持续创造高质量的社区和项目。我觉得非常有代表性,值得思考。web3项目都是去中心化的,项……