理解C#泛型运作原理 (2)

那么测试代码则改写为如下:

var arrayStr = new ArrayExpandable<string>(); var strs = new string[] { "ryzen", "reed", "wymen", "gavin" }; for (int i = 0; i < strs.Length; i++) { arrayStr.Add(strs[i]); string value = arrayStr[i];//改为int value = arrayStr[i] 编译报错 Console.WriteLine(value); } Console.WriteLine($"Now {nameof(arrayStr)} Capacity:{arrayStr.Capacity}"); var array = new ArrayExpandable<int>(); for (int i = 0; i < 5; i++) { array.Add(i); int value = array[i]; Console.WriteLine(value); } Console.WriteLine($"Now {nameof(array)} Capacity:{array.Capacity}");

输出:

ryzen reed wymen gavin Now arrayStr Capacity:4 0 1 2 3 4 Now array Capacity:8

我们通过截取部分ArrayExpandable<T>的IL查看其本质是个啥:

//声明类 .class public auto ansi beforefieldinit MetaTest.ArrayExpandable`1<T> extends [System.Runtime]System.Object { .custom instance void [System.Runtime]System.Reflection.DefaultMemberAttribute::.ctor(string) = ( 01 00 04 49 74 65 6D 00 00 ) } //Add方法 .method public hidebysig instance void Add(!T 'value') cil managed { // 代码大小 69 (0x45) .maxstack 3 .locals init (bool V_0) IL_0000: nop IL_0001: ldarg.0 IL_0002: ldfld int32 class MetaTest.ArrayExpandable`1<!T>::_size IL_0007: ldarg.0 IL_0008: ldfld !0[] class MetaTest.ArrayExpandable`1<!T>::_items IL_000d: ldlen IL_000e: conv.i4 IL_000f: ceq IL_0011: stloc.0 IL_0012: ldloc.0 IL_0013: brfalse.s IL_0024 IL_0015: ldarg.0 IL_0016: ldarg.0 IL_0017: ldfld int32 class MetaTest.ArrayExpandable`1<!T>::_size IL_001c: ldc.i4.1 IL_001d: add IL_001e: call instance void class MetaTest.ArrayExpandable`1<!T>::EnsuresCapacity(int32) IL_0023: nop IL_0024: ldarg.0 IL_0025: ldfld !0[] class MetaTest.ArrayExpandable`1<!T>::_items IL_002a: ldarg.0 IL_002b: ldfld int32 class MetaTest.ArrayExpandable`1<!T>::_size IL_0030: ldarg.1 IL_0031: stelem !T IL_0036: ldarg.0 IL_0037: ldarg.0 IL_0038: ldfld int32 class MetaTest.ArrayExpandable`1<!T>::_size IL_003d: ldc.i4.1 IL_003e: add IL_003f: stfld int32 class MetaTest.ArrayExpandable`1<!T>::_size IL_0044: ret } // end of method ArrayExpandable`1::Add

 原来定义的时候就是用了个T作为占位符,起一个模板的作用,我们对其实例化类型参数的时候,补足那个占位符,我们可以在编译期就知道了其类型,且不用在运行时进行类型检测,而我们也可以对比ArrayExpandable和ArrayExpandable<T>在类型为值类型中的IL,查看是否进行拆箱和装箱操作,以下为IL截取部分:

ArrayExpandable:

IL_0084: newobj instance void GenericSample.ArrayExpandable::.ctor() IL_0089: stloc.2 IL_008a: ldc.i4.0 IL_008b: stloc.s V_6 IL_008d: br.s IL_00bc IL_008f: nop IL_0090: ldloc.2 IL_0091: ldloc.s V_6 IL_0093: box [System.Runtime]System.Int32 //box为装箱操作 IL_0098: callvirt instance void GenericSample.ArrayExpandable::Add(object) IL_009d: nop IL_009e: ldloc.2 IL_009f: ldloc.s V_6 IL_00a1: callvirt instance object GenericSample.ArrayExpandable::get_Item(int32) IL_00a6: unbox.any [System.Runtime]System.Int32 //unbox为拆箱操作

ArrayExpandable:

IL_007f: newobj instance void class GenericSample.ArrayExpandable`1<int32>::.ctor() IL_0084: stloc.2 IL_0085: ldc.i4.0 IL_0086: stloc.s V_6 IL_0088: br.s IL_00ad IL_008a: nop IL_008b: ldloc.2 IL_008c: ldloc.s V_6 IL_008e: callvirt instance void class GenericSample.ArrayExpandable`1<int32>::Add(!0) IL_0093: nop IL_0094: ldloc.2 IL_0095: ldloc.s V_6 IL_0097: callvirt instance !0 class GenericSample.ArrayExpandable`1<int32>::get_Item(int32)

 我们从IL也能看的出来,ArrayExpandable<T>的T作为一个类型参数,在编译后在IL已经确定了其类型,因此当然也就不存在装拆箱的情况,在编译期的时候IDE能够检测类型,因此也就不用在运行时进行类型检测,但并不代表不能通过运行时检测类型(可通过is和as),还能通过反射体现出泛型的灵活性,后面会讲到

 其实有了解ArrayList和List的朋友就知道,ArrayExpandable和ArrayExpandable<T>其实现大致就是和它们一样,只是简化了很多的版本,我们这里可以通过 BenchmarkDotNet 来测试其性能对比,代码如下:

[SimpleJob(RuntimeMoniker.NetCoreApp31,baseline:true)] [SimpleJob(RuntimeMoniker.NetCoreApp50)] [MemoryDiagnoser] public class TestClass { [Benchmark] public void EnumAE_ValueType() { ArrayExpandable array = new ArrayExpandable(); for (int i = 0; i < 10000; i++) { array.Add(i);//装箱 int value = (int)array[i];//拆箱 } array = null;//确保进行垃圾回收 } [Benchmark] public void EnumAE_RefType() { ArrayExpandable array = new ArrayExpandable(); for (int i = 0; i < 10000; i++) { array.Add("r"); string value = (string)array[i]; } array = null;//确保进行垃圾回收 } [Benchmark] public void EnumAE_Gen_ValueType() { ArrayExpandable<int> array = new ArrayExpandable<int>(); for (int i = 0; i < 10000; i++) { array.Add(i); int value = array[i]; } array = null;//确保进行垃圾回收; } [Benchmark] public void EnumAE_Gen_RefType() { ArrayExpandable<string> array = new ArrayExpandable<string>(); for (int i = 0; i < 10000; i++) { array.Add("r"); string value = array[i]; } array = null;//确保进行垃圾回收; } [Benchmark] public void EnumList_ValueType() { List<int> array = new List<int>(); for (int i = 0; i < 10000; i++) { array.Add(i); int value = array[i]; } array = null;//确保进行垃圾回收; } [Benchmark] public void EnumList_RefType() { List<string> array = new List<string>(); for (int i = 0; i < 10000; i++) { array.Add("r"); string value = array[i]; } array = null;//确保进行垃圾回收; } [Benchmark(Baseline =true)] public void EnumAraayList_valueType() { ArrayList array = new ArrayList(); for (int i = 0; i < 10000; i++) { array.Add(i); int value = (int)array[i]; } array = null;//确保进行垃圾回收; } [Benchmark] public void EnumAraayList_RefType() { ArrayList array = new ArrayList(); for (int i = 0; i < 10000; i++) { array.Add("r"); string value = (string)array[i]; } array = null;//确保进行垃圾回收; } }

 我还加入了.NETCore3.1和.NET5的对比,且以.NETCore3.1的EnumAraayList_valueType方法为基准,性能测试结果如下:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zygfds.html