eax币最新价格,fo币怎么样

  

  现代程序员写代码,没人敢说没用过泛型。这个通用模板T可以被您想要的任何类型替换。真的很神奇很神奇,很多人都习惯了。但这只是泛型T的底层是如何帮助你认识到这一点的,这是非常有趣的。不知道有多少人知道底层怎么玩,我就试着分享这一个。也不一定全对。   

  

  一、没有泛型前   

  

  现在。网芯3.1及最新。NET Framework 8不再有原来被人诟病的ArrayList了,但这个东西不得不说也是巧合,因为它决定了C#团队会彻底忏悔,抛弃过去重新上路。数组列表案例代码的最后一段。   

  

  public class ArrayList { private object items;private int index=0;public ArrayList(){ items=new object 10;} public void Add(对象项){ items=item}}上面的代码,为了保证各种类型的eg: int,double,class可以插入Add,想出了一个用祖先类对象接收的招数,这就引入了两大问题,打包和解包和类型安全。   

  

  1、装箱拆箱   

  

  这很好理解,因为你使用了祖先类,所以当你添加值类型时,自然会有装箱操作,比如下面的代码:   

  

  ArrayList ArrayList=new ArrayList();数组列表。增加(3);1 占用更大的空间   

  

  我准备用windbg来看看这个问题。相信大家都知道一个int类型占用4个字节,那么堆里装了多少个字节呢?好奇。   

  

  原始代码和IL代码如下:   

  

  公共静态void Main(string args){ var num=10;var obj=(object)num;控制台。read();}   

  

  IL _ 0000: no pil _ 0001: LDC . i4 . s 10IL _ 0003: stloc . 0il _ 0004: ldloc . 0il _ 0005:箱系统。int 32 il _ 000 a : stloc . 1il _ 000 b :调用int3 2。系统。控制台:3360 read()IL _ 00103360 popil _ 0011: ret你可以清楚的看到IL_0005里面有一个盒子说明,包装没有问题。然后获取转储文件。   

  

  ~0s -!clrstack -l -!do0x0000018300002d48   

  

  0:000 ~0sntdll!ZwReadFile0x 14:00007 ff 9 ` fc 7 baa 64 C3 ret 0:000!clrstack -lOS线程Id:0xfc (0)子SP IP调用site 0000002 c 397 fedf 0 00007 ff 985 c 808 F3 console app 2。Program.Main(系统。string)locals 33600x 00000002 c 397 fee 2c=0x 000000000000 a0x 0000002 c 397 fee 20=0x 0000018300002d 4800000002 c 397 ff 038 00007 ff 9 e 51 b 6 c 93 0:000!do0x0000018300002d48Name:系统。int 32 method table : 00007 ff 9e 33285 a 0 eeclass : 00007 ff 9e 34958 A8 size : 24(0x 18)字节文件: C:\ WINDOWS\Microsoft。net \ assembly \ GAC _ 64 \ mscorlib \ v 4.0 _ 4 . 0 . 0 . 0 _ _ b 77 a5 c 561934 e 089 \ Mslib.dll字段3360mt字段偏移量类型VT Attr值名称0007FF 9E33285A 0 40005A08系统。Int32 1instance 10m _ value倒数第二行的大小3360 24 (0x18)字节可以清楚的看到是24字节。为什么是24字节,8(同步块指针)8(方法表指针)4(对象大小)=20,但是因为是x64位,所以内存按8对齐,   

也就是要按8的倍数计算,所以占用是 8+8+8 =24 字节,原来只有4字节的大小因为装箱已被爆到24字节,如果是10000个值类型的装箱那空间占用是不是挺可怕的?

  

<2> 栈到堆的装箱搬运到运输到售后到无害化处理都需要付出重大的人力和机器成本

  

2、类型不安全

  

很简单,因为是祖宗类型object,所以无法避免程序员使用乱七八糟的类型,当然这可能是无意的,但是编译器确无法规避,代码如下:

  

ArrayList arrayList = new ArrayList();arrayList.Add(3);arrayList.Add(new Action<int>((num) => { }));arrayList.Add(new object());面对这两大尴尬的问题,C#团队决定重新设计一个类型,实现一定终身,这就有了泛型。

  

二、泛型的出现

  

1、救世主

  

首先可以明确的说,泛型就是为了解决这两个问题而生的,你可以在底层提供的List<T>中使用List<int>,List<double>。。。等等你看得上的类型,而这种技术的底层实现原理才是本篇关注的重点。

  

public static void Main(string<> args){ List<double> list1 = new List<double>(); List<string> list3 = new List<string>(); ...}三、泛型原理探究

  

这个问题的探索其实就是 List<T> -> List<int>在何处实现了 T -> int 的替换,反观java,它的泛型实现其实在底层还是用object来替换的,C#肯定不是这么做的,不然也没这篇文章啦,要知道在哪个阶段被替换了,你起码要知道C#代码编译的几个阶段,为了理解方便,我画一张图吧。

  


  

  


  

流程大家也看到了,要么在MSIL中被替换,要么在JIT编译中被替换。

  

public static void Main(string<> args){ List<double> list1 = new List<double>(); List<int> list2 = new List<int>(); List<string> list3 = new List<string>(); List<int<>> list4 = new List<int<>>(); Console.ReadLine();}1、在第一阶段探究

  

因为第一阶段是MSIL代码,所以用ILSpy看一下中间代码即可。

  

IL_0000: nopIL_0001: newobj instance void class System.Collections.Generic.List`1<float64>::.ctor()IL_0006: stloc.0IL_0007: newobj instance void class System.Collections.Generic.List`1<int32>::.ctor()IL_000c: stloc.1IL_000d: newobj instance void class System.Collections.Generic.List`1<string>::.ctor()IL_0012: stloc.2IL_0013: newobj instance void class System.Collections.Generic.List`1<int32<>>::.ctor()IL_0018: stloc.3IL_0019: call string System.Console::ReadLine()IL_001e: popIL_001f: ret.class public auto ansi serializable beforefieldinit System.Collections.Generic.List`1<T>extends System.Objectimplements class System.Collections.Generic.IList`1<!T>, class System.Collections.Generic.ICollection`1<!T>, class System.Collections.Generic.IEnumerable`1<!T>, System.Collections.IEnumerable, System.Collections.IList, System.Collections.ICollection, class System.Collections.Generic.IReadOnlyList`1<!T>, class System.Collections.Generic.IReadOnlyCollection`1<!T>从上面的IL代码中可以看到,最终的类定义还是 System.Collections.Generic.List1\<T>,说明在中间代码阶段还是没有实现 T -> int 的替换。

  

2、在第二阶段探究

  

想看到JIT编译后的代码,这个说难也不难,其实每个对象头上都有一个方法表指针,而这个指针指向的就是方法表,方法表中有该类型的所有最终生成方法,如果不好理解,我就画个图。

  


  

  


  

!dumpheap -stat 寻找托管堆上的四个List对象。

  


  

0:000> !dumpheap -statStatistics: MT Count TotalSize Class Name00007ff9e3314320 1 32 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle00007ff9e339b4b8 1 40 System.Collections.Generic.List`1<>00007ff9e333a068 1 40 System.Collections.Generic.List`1<>00007ff9e3330d58 1 40 System.Collections.Generic.List`1<>00007ff9e3314a58 1 40 System.IO.Stream+NullStream00007ff9e3314510 1 40 Microsoft.Win32.Win32Native+InputRecord00007ff9e3314218 1 40 System.Text.InternalEncoderBestFitFallback00007ff985b442c0 1 40 System.Collections.Generic.List`1<, mscorlib>>00007ff9e338fd28 1 48 System.Text.DBCSCodePageEncoding+DBCSDecoder00007ff9e3325ef0 1 48 System.SharedStatics

  

可以看到从托管堆中找到了4个list对象,现在我就挑一个最简单的 System.Collections.Generic.List1<> ,前面的 00007ff9e333a068 就是方法表地址。

  


  

!dumpmt -md 00007ff9e333a068

  


  

0:000> !dumpmt -md 00007ff9e333a068EEClass: 00007ff9e349b008Module: 00007ff9e3301000Name: System.Collections.Generic.List`1<>mdToken: 00000000020004afFile: C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dllBaseSize: 0x28ComponentSize: 0x0Slots in VTable: 77Number of IFaces in IFaceMap: 8--------------------------------------MethodDesc Table Entry MethodDesc JIT Name00007ff9e3882450 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.List`1<>.Add(Int32)00007ff9e4202dc0 00007ff9e34dc7f8 PreJIT System.Collections.Generic.List`1<>.Insert(Int32, Int32)

  

上面方法表中的方法过多,我做了一下删减,可以清楚的看到,此时Add方法已经接受(Int32)类型的数据了,说明在JIT编译之后,终于实现了 T -> int 的替换,然后再把 List<double> 打出来看一下。

  


  

0:000> !dumpmt -md 00007ff9e339b4b8MethodDesc Table Entry MethodDesc JIT Name00007ff9e3882450 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.List`1<>.Add(Double)00007ff9e3867a00 00007ff9e34e4280 PreJIT System.Collections.Generic.List`1<>.Insert(Int32, Double)

  

上面看的都是值类型,接下来再看一下如果 T 是引用类型会是怎么样呢?

  


  

0:000> !dumpmt -md 00007ff9e3330d58MethodDesc Table Entry MethodDesc JIT Name00007ff9e3890060 00007ff9e34eb058 PreJIT System.Collections.Generic.List`1<>.Add(System.__Canon)0:000> !dumpmt -md 00007ff985b442c0MethodDesc Table Entry MethodDesc JIT Name00007ff9e3890060 00007ff9e34eb058 PreJIT System.Collections.Generic.List`1<>.Add(System.__Canon)

  

可以看到当是List<int<>> 和 List<string> 的时候,JIT使用了 System.__Canon 这么一个类型作为替代,有可能人家是摄影爱好者吧,为什么用__Canon替代引用类型,这是因为它想让能共享代码区域的方法都共享来节省空间和内存吧,不信的话可以看看它们的Entry列都是同一个内存地址:00007ff9e3890060, 打印出来就是这么一段汇编。

  


  

0:000> !u 00007ff9e3890060preJIT generated codeSystem.Collections.Generic.List`1<>.Add(System.__Canon)Begin 00007ff9e3890060, size 4a>>> 00007ff9`e3890060 57 push rdi00007ff9`e3890061 56 push rsi00007ff9`e3890062 4883ec28 sub rsp,28h00007ff9`e3890066 488bf1 mov rsi,rcx00007ff9`e3890069 488bfa mov rdi,rdx00007ff9`e389006c 8b4e18 mov ecx,dword ptr 00007ff9`e389006f 488b5608 mov rdx,qword ptr 00007ff9`e3890073 3b4a08 cmp ecx,dword ptr 00007ff9`e3890076 7422 je mscorlib_ni+0x59009a (00007ff9`e389009a)00007ff9`e3890078 488b4e08 mov rcx,qword ptr 00007ff9`e389007c 8b5618 mov edx,dword ptr 00007ff9`e389007f 448d4201 lea r8d,00007ff9`e3890083 44894618 mov dword ptr ,r8d00007ff9`e3890087 4c8bc7 mov r8,rdi00007ff9`e389008a ff152088faff call qword ptr (JitHelp: CORINFO_HELP_ARRADDR_ST)00007ff9`e3890090 ff461c inc dword ptr 00007ff9`e3890093 4883c428 add rsp,28h00007ff9`e3890097 5e pop rsi00007ff9`e3890098 5f pop rdi00007ff9`e3890099 c3 ret00007ff9`e389009a 8b5618 mov edx,dword ptr 00007ff9`e389009d ffc2 inc edx00007ff9`e389009f 488bce mov rcx,rsi00007ff9`e38900a2 90 nop00007ff9`e38900a3 e8c877feff call mscorlib_ni+0x577870 (00007ff9`e3877870) (System.Collections.Generic.List`1<>.EnsureCapacity(Int32), mdToken: 00000000060039e5)00007ff9`e38900a8 ebce jmp mscorlib_ni+0x590078 (00007ff9`e3890078)

  

然后再回过头看List<int> 和 List<double> ,从Entry列中看确实不是一个地址,说明List<int> 和 List<double> 是两个完全不一样的Add方法,看得懂汇编的可以自己看一下哈。

  


  

MethodDesc Table Entry MethodDesc JIT Name00007ff9e38a3650 00007ff9e34dc6e8 PreJIT System.Collections.Generic.List`1<>.Add(Int32)00007ff9e4428730 00007ff9e34e4170 PreJIT System.Collections.Generic.List`1<>.Add(Double)0:000> !u 00007ff9e38a3650preJIT generated codeSystem.Collections.Generic.List`1<>.Add(Int32)Begin 00007ff9e38a3650, size 50>>> 00007ff9`e38a3650 57 push rdi00007ff9`e38a3651 56 push rsi00007ff9`e38a3652 4883ec28 sub rsp,28h00007ff9`e38a3656 488bf1 mov rsi,rcx00007ff9`e38a3659 8bfa mov edi,edx00007ff9`e38a365b 8b5618 mov edx,dword ptr 00007ff9`e38a365e 488b4e08 mov rcx,qword ptr 00007ff9`e38a3662 3b5108 cmp edx,dword ptr 00007ff9`e38a3665 7423 je mscorlib_ni+0x5a368a (00007ff9`e38a368a)00007ff9`e38a3667 488b5608 mov rdx,qword ptr 00007ff9`e38a366b 8b4e18 mov ecx,dword ptr 00007ff9`e38a366e 8d4101 lea eax,00007ff9`e38a3671 894618 mov dword ptr ,eax00007ff9`e38a3674 3b4a08 cmp ecx,dword ptr 00007ff9`e38a3677 7321 jae mscorlib_ni+0x5a369a (00007ff9`e38a369a)00007ff9`e38a3679 4863c9 movsxd rcx,ecx00007ff9`e38a367c 897c8a10 mov dword ptr ,edi00007ff9`e38a3680 ff461c inc dword ptr 00007ff9`e38a3683 4883c428 add rsp,28h00007ff9`e38a3687 5e pop rsi00007ff9`e38a3688 5f pop rdi00007ff9`e38a3689 c3 ret00007ff9`e38a368a 8b5618 mov edx,dword ptr 00007ff9`e38a368d ffc2 inc edx00007ff9`e38a368f 488bce mov rcx,rsi00007ff9`e38a3692 90 nop00007ff9`e38a3693 e8a8e60700 call mscorlib_ni+0x621d40 (00007ff9`e3921d40) (System.Collections.Generic.List`1<>.EnsureCapacity(Int32), mdToken: 00000000060039e5)00007ff9`e38a3698 ebcd jmp mscorlib_ni+0x5a3667 (00007ff9`e38a3667)00007ff9`e38a369a e8bf60f9ff call mscorlib_ni+0x53975e (00007ff9`e383975e) (mscorlib_ni)00007ff9`e38a369f cc int 30:000> !u 00007ff9e4428730preJIT generated codeSystem.Collections.Generic.List`1<>.Add(Double)Begin 00007ff9e4428730, size 5a>>> 00007ff9`e4428730 56 push rsi00007ff9`e4428731 4883ec20 sub rsp,20h00007ff9`e4428735 488bf1 mov rsi,rcx00007ff9`e4428738 8b5618 mov edx,dword ptr 00007ff9`e442873b 488b4e08 mov rcx,qword ptr 00007ff9`e442873f 3b5108 cmp edx,dword ptr 00007ff9`e4428742 7424 je mscorlib_ni+0x1128768 (00007ff9`e4428768)00007ff9`e4428744 488b5608 mov rdx,qword ptr 00007ff9`e4428748 8b4e18 mov ecx,dword ptr 00007ff9`e442874b 8d4101 lea eax,00007ff9`e442874e 894618 mov dword ptr ,eax00007ff9`e4428751 3b4a08 cmp ecx,dword ptr 00007ff9`e4428754 732e jae mscorlib_ni+0x1128784 (00007ff9`e4428784)00007ff9`e4428756 4863c9 movsxd rcx,ecx00007ff9`e4428759 f20f114cca10 movsd mmword ptr ,xmm100007ff9`e442875f ff461c inc dword ptr 00007ff9`e4428762 4883c420 add rsp,20h00007ff9`e4428766 5e pop rsi00007ff9`e4428767 c3 ret00007ff9`e4428768 f20f114c2438 movsd mmword ptr ,xmm100007ff9`e442876e 8b5618 mov edx,dword ptr 00007ff9`e4428771 ffc2 inc edx00007ff9`e4428773 488bce mov rcx,rsi00007ff9`e4428776 90 nop00007ff9`e4428777 e854fbffff call mscorlib_ni+0x11282d0 (00007ff9`e44282d0) (System.Collections.Generic.List`1<>.EnsureCapacity(Int32), mdToken: 00000000060039e5)00007ff9`e442877c f20f104c2438 movsd xmm1,mmword ptr 00007ff9`e4428782 ebc0 jmp mscorlib_ni+0x1128744 (00007ff9`e4428744)00007ff9`e4428784 e8d50f41ff call mscorlib_ni+0x53975e (00007ff9`e383975e) (mscorlib_ni)00007ff9`e4428789 cc int 3

  

可能你有点蒙,我画一张图吧。

  


  

  


  

四、总结

  


  

泛型T真正的被代替是在 JIT编译时才实现的,四个List<T> 会生成四个具有相应具体类型的类对象,所以就不存在拆箱和装箱的问题,而类型的限定visualstudio编译器工具提前就帮我们约束好啦。

相关文章