前言

 我们都知道泛型在C#的重要性,泛型是OOP语言中三大特征的多态的最重要的体现,几乎泛型撑起了整个.NET框架,在讲泛型之前,我们可以抛出一个问题,我们现在需要一个可扩容的数组类,且满足所有类型,不管是值类型还是引用类型,那么在没有用泛型方法实现,如何实现?

一.泛型之前的故事

 我们肯定会想到用object来作为类型参数,因为在C#中,所有类型都是基于Object类型的。因此Object是所有类型的最基类,那么我们的可扩容数组类如下:

Copy public class ArrayExpandable{private object?[] _items = null;private int _defaultCapacity = 4;private int _size;public object? this[int index]{get{if (index < 0 || index >= _size) throw new ArgumentOutOfRangeException(nameof(index));return _items[index];}set{if (index < 0 || index >= _size) throw new ArgumentOutOfRangeException(nameof(index));_items[index] = value;}}public int Capacity{get => _items.Length;set{if (value < _size){throw new ArgumentOutOfRangeException(nameof(value));}if (value != _items.Length){if (value > 0){object[] newItems = new object[value];if (_size > 0){Array.Copy(_items, newItems, _size);}_items = newItems;}else{_items = new object[_defaultCapacity];}}}}public int Count => _size;public ArrayExpandable(){_items = new object?[0];}public ArrayExpandable(int capacity){_items = new object?[capacity];}public void Add(object? value){//数组元素为0或者数组元素容量满if (_size == _items.Length) EnsuresCapacity(_size + 1);_items[_size] = value;_size++;}private void EnsuresCapacity(int size){if (_items.Length < size){int newCapacity = _items.Length == 0 ? _defaultCapacity : _items.Length * 2;if (newCapacity < size) newCapacity = size;Capacity = newCapacity;}}

然后我们来验证下:

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

输出:

Copyryzen
reed
wymen
gavin
Now arrayStr Capacity:4
Now array Capacity:8

 貌似输出结果是正确的,能够动态进行扩容,同样的支持值类型Structint32和引用类型的字符串,但是其实这里会发现一些问题,那就是

  1. 引用类型string进行了类型转换的验证

  2. 值类型int32进行了装箱和拆箱操作,同时进行类型转换类型的检验

  3. 发生的这一切都是在运行时的,假如类型转换错误,得在运行时才能报错

大致执行模型如下:

引用类型:

值类型:

 那么有没有一种方法能够避免上面遇到的三种问题呢?在借鉴了cpp的模板和java的泛型经验,在C#2.0的时候推出了更适合.NET体系下的泛型

二.用泛型实现

Copypublic class ArrayExpandable<T>
{private T[] _items;private int _defaultCapacity = 4;private int _size;public T this[int index]{get{if (index < 0 || index >= _size) throw new ArgumentOutOfRangeException(nameof(index));return _items[index];}set{if (index < 0 || index >= _size) throw new ArgumentOutOfRangeException(nameof(index));_items[index] = value;}}public int Capacity{get => _items.Length;set{if (value < _size){throw new ArgumentOutOfRangeException(nameof(value));}if (value != _items.Length){if (value > 0){T[] newItems = new T[value];if (_size > 0){Array.Copy(_items, newItems, _size);}_items = newItems;}else{_items = new T[_defaultCapacity];}}}}public int Count => _size;public ArrayExpandable(){_items = new T[0];}public ArrayExpandable(int capacity){_items = new T[capacity];}public void Add(T value){//数组元素为0或者数组元素容量满if (_size == _items.Length) EnsuresCapacity(_size + 1);_items[_size] = value;_size++;}private void EnsuresCapacity(int size){if (_items.Length < size){int newCapacity = _items.Length == 0 ? _defaultCapacity : _items.Length * 2;if (newCapacity < size) newCapacity = size;Capacity = newCapacity;}}}

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

Copyvar 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}");

输出:

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

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

Copy//声明类
.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:  nopIL_0001:  ldarg.0IL_0002:  ldfld      int32 class MetaTest.ArrayExpandable`1<!T>::_sizeIL_0007:  ldarg.0IL_0008:  ldfld      !0[] class MetaTest.ArrayExpandable`1<!T>::_itemsIL_000d:  ldlenIL_000e:  conv.i4IL_000f:  ceqIL_0011:  stloc.0IL_0012:  ldloc.0IL_0013:  brfalse.s  IL_0024IL_0015:  ldarg.0IL_0016:  ldarg.0IL_0017:  ldfld      int32 class MetaTest.ArrayExpandable`1<!T>::_sizeIL_001c:  ldc.i4.1IL_001d:  addIL_001e:  call       instance void class MetaTest.ArrayExpandable`1<!T>::EnsuresCapacity(int32)IL_0023:  nopIL_0024:  ldarg.0IL_0025:  ldfld      !0[] class MetaTest.ArrayExpandable`1<!T>::_itemsIL_002a:  ldarg.0IL_002b:  ldfld      int32 class MetaTest.ArrayExpandable`1<!T>::_sizeIL_0030:  ldarg.1IL_0031:  stelem     !TIL_0036:  ldarg.0IL_0037:  ldarg.0IL_0038:  ldfld      int32 class MetaTest.ArrayExpandable`1<!T>::_sizeIL_003d:  ldc.i4.1IL_003e:  addIL_003f:  stfld      int32 class MetaTest.ArrayExpandable`1<!T>::_sizeIL_0044:  ret
} // end of method ArrayExpandable`1::Add

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

ArrayExpandable:

Copy  IL_0084:  newobj     instance void GenericSample.ArrayExpandable::.ctor()IL_0089:  stloc.2IL_008a:  ldc.i4.0IL_008b:  stloc.s    V_6IL_008d:  br.s       IL_00bcIL_008f:  nopIL_0090:  ldloc.2IL_0091:  ldloc.s    V_6IL_0093:  box        [System.Runtime]System.Int32 //box为装箱操作IL_0098:  callvirt   instance void GenericSample.ArrayExpandable::Add(object)IL_009d:  nopIL_009e:  ldloc.2IL_009f:  ldloc.s    V_6IL_00a1:  callvirt   instance object GenericSample.ArrayExpandable::get_Item(int32)IL_00a6:  unbox.any  [System.Runtime]System.Int32 //unbox为拆箱操作

ArrayExpandable:

Copy IL_007f:  newobj     instance void class GenericSample.ArrayExpandable`1<int32>::.ctor()IL_0084:  stloc.2IL_0085:  ldc.i4.0IL_0086:  stloc.s    V_6IL_0088:  br.s       IL_00adIL_008a:  nopIL_008b:  ldloc.2IL_008c:  ldloc.s    V_6IL_008e:  callvirt  instance void class GenericSample.ArrayExpandable`1<int32>::Add(!0)IL_0093:  nopIL_0094:  ldloc.2IL_0095:  ldloc.s    V_6IL_0097:  callvirt   instance !0 class GenericSample.ArrayExpandable`1<int32>::get_Item(int32)

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

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

Copy    [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方法为基准,性能测试结果如下:

用更直观的柱形图来呈现:

 我们能看到在这里List的性能在引用类型和值类型中都是所以当中是最好的,不管是执行时间、GC次数,分配的内存空间大小,都是最优的,同时.NET5在几乎所有的方法中性能都是优于.NETCore3.1,这里还提一句,我实现的ArrayExpandableArrayExpandable<T>性能都差于ArrayListList,我还没实现IList和各种方法,只能说句dotnet基金会牛逼

三.泛型的多态性

多态的声明

类、结构、接口、方法、和委托可以声明一个或者多个类型参数,我们直接看代码:

Copyinterface IFoo<InterfaceT>
{void InterfaceMenthod(InterfaceT interfaceT);
}class Foo<ClassT, ClassT1>: IFoo<StringBuilder>
{public ClassT1 Field;public delegate void MyDelegate<DelegateT>(DelegateT delegateT);public void DelegateMenthod<DelegateT>(DelegateT delegateT, MyDelegate<DelegateT> myDelegate){myDelegate(delegateT);}public static string operator +(Foo<ClassT, ClassT1> foo,string s){return $"{s}:{foo.GetType().Name}";}public List<ClassT> Property{ get; set; }public ClassT1 Property1 { get; set; }public ClassT this[int index] => Property[index];//没判断越界public Foo(List<ClassT> classT, ClassT1 classT1){Property = classT;Property1 = classT1;Field = classT1;Console.WriteLine($"构造函数:parameter1 type:{Property.GetType().Name},parameter2 type:{Property1.GetType().Name}");}//方法声明了多个新的类型参数public void Method<MenthodT, MenthodT1>(MenthodT menthodT, MenthodT1 menthodT1){Console.WriteLine($"Method<MenthodT, MenthodT1>:{(menthodT.GetType().Name)}:{menthodT.ToString()}," +$"{menthodT1.GetType().Name}:{menthodT1.ToString()}");}public void Method(ClassT classT){Console.WriteLine($"{nameof(Method)}:{classT.GetType().Name}:classT?.ToString()");}public void InterfaceMenthod(StringBuilder interfaceT){Console.WriteLine(interfaceT.ToString());}
}

控制台测试代码:

Copystatic void Main(string[] args)
{Test();Console.ReadLine();
}static void Test()
{var list = new List<int>() { 1, 2, 3, 4 };var foo = new Foo<int, string>(list, "ryzen");var index = 0;Console.WriteLine($"索引:索引{index}的值:{foo[index]}");Console.WriteLine($"Filed:{foo.Field}");foo.Method(2333);foo.Method<DateTime, long>(DateTime.Now, 2021);foo.DelegateMenthod<string>("this is a delegate", DelegateMenthod);foo.InterfaceMenthod(new StringBuilder().Append("InterfaceMenthod:this is a interfaceMthod"));Console.WriteLine(foo+"重载+运算符");
}static void DelegateMenthod(string str)
{Console.WriteLine($"{nameof(DelegateMenthod)}:{str}");
}

输出如下:

Copy构造函数:parameter1 type:List`1,parameter2 type:String
索引:索引0的值:1
Filed:ryzen
Method:Int32:classT?.ToString()
Method<MenthodT, MenthodT1>:DateTime:2021/03/02 11:45:40,Int64:2021
DelegateMenthod:this is a delegate
InterfaceMenthod:this is a interfaceMthod
重载+运算符:Foo`2

我们通过例子可以看到的是:

  • 类(结构也可以),接口,委托,方法都可以声明一个或多个类型参数,体现了声明的多态性

  • 类的函数成员:属性,字段,索引,构造器,运算符只能引入类声明的类型参数,不能够声明,唯有方法这一函数成员具备声明和引用类型参数两种功能,由于具备声明功能,因此可以声明和委托一样的类型参数并且引用它,这也体现了方法的多态性

多态的继承

父类和实现类或接口的接口都可以是实例化类型,直接看代码:

Copyinterface IFooBase<IBaseT>{}interface IFoo<InterfaceT>: IFooBase<string>
{void InterfaceMenthod(InterfaceT interfaceT);
}class FooBase<ClassT>
{}class Foo<ClassT, ClassT1>: FooBase<ClassT>,IFoo<StringBuilder>{}

我们可以通过例子看出:

  • 由于Foo的基类FooBase定义的和Foo有着共享的类型参数ClassT,因此可以在继承的时候不实例化类型

  • FooIFoo接口没定义相同的类型参数,因此可以在继承的时候实例化出接口的类型参数StringBuild出来

  • IFooIFooBase没定义相同的类型参数,因此可以在继承的时候实例化出接口的类型参数string出来

  • 上述都体现出继承的多态性

多态的递归

我们定义如下一个类和一个方法,且不会报错:

Copy    class D<T> { }class C<T> : D<C<C<T>>> { void Foo(){var foo = new C<C<T>>();Console.WriteLine(foo.ToString());}}

因为T能在实例化的时候确定其类型,因此也支持这种循环套用自己的类和方法的定义

四.泛型的约束

where的约束

我们先上代码:

Copy    class FooBase{ }class Foo : FooBase {}class someClass<T,K> where T:struct where K :FooBase,new(){}static void TestConstraint(){var someClass = new someClass<int, Foo>();//通过编译//var someClass = new someClass<string, Foo>();//编译失败,string不是struct类型//var someClass = new someClass<string, long>();//编译失败,long不是FooBase类型}

再改动下Foo类:

Copyclass Foo : FooBase
{public Foo(string str){}
}static void TestConstraint()
{var someClass = new someClass<int, Foo>();//编译失败,因为new()约束必须类含有一个无参构造器,可以再给Foo类加上个无参构造器就能编译通过
}

 我们可以看到,通过where语句,可以对类型参数进行约束,而且一个类型参数支持多个约束条件(例如K),使其在实例化类型参数的时候,必须按照约束的条件对应实例符合条件的类型,而where条件约束的作用就是起在编译期约束类型参数的作用

out和in的约束

 说到outin之前,我们可以说下协变和逆变,在C#中,只有泛型接口和泛型委托可以支持协变和逆变

协变

我们先看下代码:

Copyclass FooBase{ }class Foo : FooBase
{}interface IBar<T>
{T GetValue(T t);
}class Bar<T> : IBar<T>
{public T GetValue(T t){return t;}
}static void Test()
{var foo = new Foo();FooBase fooBase = foo;//编译成功IBar<Foo> bar = new Bar<Foo>();IBar<FooBase> bar1 = bar;//编译失败}

 这时候你可能会有点奇怪,为啥那段代码会编译失败,明明Foo类可以隐式转为FooBase,但作为泛型接口类型参数实例化却并不能呢?使用out约束泛型接口IBar的T,那段代码就会编译正常,但是会引出另外一段编译报错:

Copyinterface IBar<out T>
{T GetValue(string str);//编译成功//T GetValue(T t);//编译失败 T不能作为形参输入,用out约束T支持协变,T可以作为返回值输出}IBar<Foo> bar = new Bar<Foo>();
IBar<FooBase> bar1 = bar;//编译正常

因此我们可以得出以下结论:

  • 由于Foo继承FooBase,本身子类Foo包含着父类允许访问的成员,因此能隐式转换父类,这是类型安全的转换,因此叫协变

  • 在为泛型接口用out标识其类型参数支持协变后,约束其方法的返回值和属性的Get(本质也是个返回值的方法)才能引用所声明的类型参数,也就是作为输出值,用out很明显的突出了这一意思

而支持迭代的泛型接口IEnumerable也是这么定义的:

Copy    public interface IEnumerable<out T> : IEnumerable{new IEnumerator<T> GetEnumerator();}

逆变

我们将上面代码改下:

Copyclass FooBase{ }class Foo : FooBase
{}interface IBar<T>
{T GetValue(T t);
}class Bar<T> : IBar<T>
{public T GetValue(T t){return t;}
}static void Test1()
{var fooBase = new FooBase();Foo foo = (Foo)fooBase;//编译通过,运行时报错IBar<FooBase> bar = new Bar<FooBase>();IBar<Foo> bar1 = (IBar<Foo>)bar;//编译通过,运行时报错
}

我们再改动下IBar,发现出现另外一处编译失败

Copyinterface IBar<in T>
{void GetValue(T t);//编译成功//T GetValue(T t);//编译失败 T不能作为返回值输出,用in约束T支持逆变,T可以作为返回值输出
}IBar<FooBase> bar = new Bar<FooBase>();IBar<Foo> bar1 = (IBar<Foo>)bar;//编译通过,运行时不报错IBar<Foo> bar1 = bar;//编译通过,运行时不报错

因此我们可以得出以下结论:

  • 由于FooBaseFoo的父类,并不包含子类的自由的成员,转为为子类Foo是类型不安全的,因此在运行时强式转换的报错了,但编译期是不能够确认的

  • 在为泛型接口用in标识其类型参数支持逆变后,in约束其接口成员不能将其作为返回值(输出值),我们会发现协变和逆变正是一对反义词

  • 这里提一句,值类型是不支持协变和逆变的

同样的泛型委托Action就是个逆变的例子:

Copypublic delegate void Action<in T>(T obj);

五.泛型的反射

我们先来看看以下代码:

Copystatic void Main(string[] args)
{var lsInt = new ArrayExpandable<int>();lsInt.Add(1);var lsStr = new ArrayExpandable<string>();lsStr.Add("ryzen");var lsStr1 = new ArrayExpandable<string>();lsStr.Add("ryzen");
}

然后通过ildasm查看其IL,开启视图-》显示标记值,查看Main方法:

Copyvoid  Main(string[] args) cil managed
{.entrypoint// 代码大小       52 (0x34).maxstack  2.locals /*11000001*/ init (class MetaTest.ArrayExpandable`1/*02000003*/<int32> V_0,class MetaTest.ArrayExpandable`1/*02000003*/<string> V_1,class MetaTest.ArrayExpandable`1/*02000003*/<string> V_2)IL_0000:  nopIL_0001:  newobj     instance void class MetaTest.ArrayExpandable`1/*02000003*/<int32>/*1B000001*/::.ctor() /* 0A00000C */IL_0006:  stloc.0IL_0007:  ldloc.0IL_0008:  ldc.i4.1IL_0009:  callvirt   instance void class MetaTest.ArrayExpandable`1/*02000003*/<int32>/*1B000001*/::Add(!0) /* 0A00000D */IL_000e:  nopIL_000f:  newobj     instance void class MetaTest.ArrayExpandable`1/*02000003*/<string>/*1B000002*/::.ctor() /* 0A00000E */IL_0014:  stloc.1IL_0015:  ldloc.1IL_0016:  ldstr      "ryzen" /* 70000001 */IL_001b:  callvirt   instance void class MetaTest.ArrayExpandable`1/*02000003*/<string>/*1B000002*/::Add(!0) /* 0A00000F */IL_0020:  nopIL_0021:  newobj     instance void class MetaTest.ArrayExpandable`1/*02000003*/<string>/*1B000002*/::.ctor() /* 0A00000E */IL_0026:  stloc.2IL_0027:  ldloc.1IL_0028:  ldstr      "ryzen" /* 70000001 */IL_002d:  callvirt   instance void class MetaTest.ArrayExpandable`1/*02000003*/<string>/*1B000002*/::Add(!0) /* 0A00000F */IL_0032:  nopIL_0033:  ret
} // end of method Program::Main

打开元数据表将上面所涉及到的元数据定义表和类型规格表列出:

metainfo:

Copy-----------定义部分
TypeDef #2 (02000003)
-------------------------------------------------------TypDefName: MetaTest.ArrayExpandable`1  (02000003)Flags     : [Public] [AutoLayout] [Class] [AnsiClass] [BeforeFieldInit]  (00100001)Extends   : 0100000C [TypeRef] System.Object1 Generic Parameters(0) GenericParamToken : (2a000001) Name : T flags: 00000000 Owner: 02000003Method #8 (0600000a) -------------------------------------------------------MethodName: Add (0600000A)Flags     : [Public] [HideBySig] [ReuseSlot]  (00000086)RVA       : 0x000021f4ImplFlags : [IL] [Managed]  (00000000)CallCnvntn: [DEFAULT]hasThis ReturnType: Void1 ArgumentsArgument #1:  Var!01 Parameters(1) ParamToken : (08000007) Name : value flags: [none] (00000000)------类型规格部分
TypeSpec #1 (1b000001)
-------------------------------------------------------TypeSpec : GenericInst Class MetaTest.ArrayExpandable`1< I4> //14代表int32MemberRef #1 (0a00000c)-------------------------------------------------------Member: (0a00000c) .ctor: CallCnvntn: [DEFAULT]hasThis ReturnType: VoidNo arguments.MemberRef #2 (0a00000d)-------------------------------------------------------Member: (0a00000d) Add: CallCnvntn: [DEFAULT]hasThis ReturnType: Void1 ArgumentsArgument #1:  Var!0TypeSpec #2 (1b000002)
-------------------------------------------------------TypeSpec : GenericInst Class MetaTest.ArrayExpandable`1< String>MemberRef #1 (0a00000e)-------------------------------------------------------Member: (0a00000e) .ctor: CallCnvntn: [DEFAULT]hasThis ReturnType: VoidNo arguments.MemberRef #2 (0a00000f)-------------------------------------------------------Member: (0a00000f) Add: CallCnvntn: [DEFAULT]hasThis ReturnType: Void1 ArgumentsArgument #1:  Var!0

 这时候我们就可以看出,元数据为泛型类ArrayExpandable<T>定义一份定义表,生成两份规格,也就是当你实例化类型参数为intstring的时候,分别生成了两份规格代码,同时还发现以下的现象:

Copyvar lsInt = new ArrayExpandable<int>();//引用的是类型规格1b000001的成员0a00000c .ctor构造
lsInt.Add(1);//引用的是类型规格1b000001的成员0a00000d Addvar lsStr = new ArrayExpandable<string>();//引用的是类型规格1b000002的成员0a00000e .ctor构造
lsStr.Add("ryzen");//引用的是类型规格1b000002的成员0a00000f Add
var lsStr1 = new ArrayExpandable<string>();//和lsStr一样
lsStr.Add("ryzen");//和lsStr一样

 非常妙的是,当你实例化两个一样的类型参数string,是共享一份类型规格的,也就是同享一份本地代码,因此上面的代码在线程堆栈和托管堆的大致是这样的:

由于泛型也有元数据的存在,因此可以对其做反射:

CopyConsole.WriteLine($"-----------{nameof(lsInt)}---------------");
Console.WriteLine($"{nameof(lsInt)} is generic?:{lsInt.GetType().IsGenericType}");
Console.WriteLine($"Generic type:{lsInt.GetType().GetGenericArguments()[0].Name}");
Console.WriteLine("---------Menthods:");
foreach (var method in lsInt.GetType().GetMethods())
{Console.WriteLine(method.Name);
}
Console.WriteLine("---------Properties:");
foreach (var property in lsInt.GetType().GetProperties())
{Console.WriteLine($"{property.PropertyType.ToString()}:{property.Name}");
}Console.WriteLine($"\n-----------{nameof(lsStr)}---------------");
Console.WriteLine($"{nameof(lsStr)} is generic?:{lsStr.GetType().IsGenericType}");
Console.WriteLine($"Generic type:{lsStr.GetType().GetGenericArguments()[0].Name}");
Console.WriteLine("---------Menthods:");
foreach (var method in lsStr.GetType().GetMethods())
{Console.WriteLine(method.Name);
}
Console.WriteLine("---------Properties:");
foreach (var property in lsStr.GetType().GetProperties())
{Console.WriteLine($"{property.PropertyType.ToString()}:{property.Name}");
}

输出:

Copy-----------lsInt---------------
lsInt is generic?:True
Generic type:Int32
---------Menthods:
get_Item
set_Item
get_Capacity
set_Capacity
get_Count
Add
GetType
ToString
Equals
GetHashCode
---------Properties:
System.Int32:Item
System.Int32:Capacity
System.Int32:Count-----------lsStr---------------
lsStr is generic?:True
Generic type:String
---------Menthods:
get_Item
set_Item
get_Capacity
set_Capacity
get_Count
Add
GetType
ToString
Equals
GetHashCode
---------Properties:
System.String:Item
System.Int32:Capacity
System.Int32:Count

六.总结

 泛型编程作为.NET体系中一个很重要的编程思想,主要有以下亮点:

  • 编译期确定类型,避免值类型的拆装箱和不必要的运行时类型检验,同样运行时也能通过isas进行类型检验

  • 通过约束进行对类型参数实例化的范围

  • 同时在IL层面,实例化相同类型参数的时候共享一份本地代码

  • 由于元数据的存在,也能在运行时进行反射,增强其灵活性

参考

Design and Implementation of Generics for the .NET Common Language Runtime

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/

《CLR Via C# 第四版》

《你必须知道的.NET(第二版)》

理解C#泛型运作原理相关推荐

  1. 十分钟理解Java泛型擦除

    泛型信息只存在于代码编译阶段,但是在java的运行期(已经生成字节码文件后)与泛型相关的信息会被擦除掉,专业术语叫做类型擦除. 今天我们来讲解泛型中另一个重要知识点--泛型擦除! 泛型擦除概念 泛型信 ...

  2. Java基础-我所理解的泛型

    Java基础-我所理解的泛型 引用 [java]泛型中,? extends T 与 ? super T 的区别.看法_winrh的博客-CSDN博客_泛型 extends 前言 Java基础系列,我所 ...

  3. AI行为树的基础运作原理

    欢迎捉虫! 之前我研究了一下基于switch case语句的FSM状态机的使用,后来遇到了很多问题. 比如当角色的行为很多时,代码结构相当混乱(你需要考虑每一种状态之间的联系). 所以,当角色的行为愈 ...

  4. 【WPS-OFFICE-Word】 WPS中样式的运作原理?样式自动更新、自动改变如何处理?样式的管理方法?

    一.WPS中样式的运作原理 文档中的每一个文字或者段落,它的格式取决于两点--样式以及自定义修改. 比如内容A基于样式1,样式1的字体格式是五号宋体.段落格式是1.5倍行距: 我们在样式1的基础上,从 ...

  5. PHP session的运作原理解析

    今天主要介绍关于PHP session的运作原理解析,内容当中有详细资料跟代码实例相结合,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 本文主要介绍了 php语言,其他语言的操作可 ...

  6. 大脑是如何记忆的?大脑记忆工作的构成和运作原理

    我们每个人每天都会接收一些信息,同时我们还会忘掉一些东西,但我们很少去了解我们大脑为什么会这样处理?所以我们深入的探究一下大脑记忆是如何工作的?了解一下大脑记忆的构成和运作原理? 我们先看记忆是什么. ...

  7. 同一个网络下两台设备看直播时,进度不是一致的?浅谈其运作原理

     目录 ​编辑 前言 开篇 概念补充 直播原理 真实场景 总结 前言 大家好哇!我是爷爷的茶七里香,发现一个很有意思的现象,想必大家也遇到过,我是在跟朋友同时看LOL总决赛直播的时候发现的,当时我跟他 ...

  8. 主机主浏览服务器宣告的运作原理机制

    主机主浏览服务器宣告的运作原理机制.刚启动的电脑会在第1,第2,第4,第8 第12 分钟向主浏览器进行主机宣告,此后每间隔12分钟进行主机宣告! 问题一   请问如果第1分钟就已经注册成功了,那是不是 ...

  9. [diango]理解django视图工作原理

    前言:正确理解django视图view,模型model,模板的概念及其之间的关联关系,才能快速学习并上手使用django制作网页 本文主要讲解自己在学习django后对视图view的理解 在进入正文之 ...

最新文章

  1. 《人类简史》作者:应对 AI 革命,要打造新的经济、社会和教育体系
  2. 支持多种小程序!阿里云ARMS推出小程序监控
  3. iOS 获取键盘相关信息
  4. 关于Scrum中sprint的规模估算的对话
  5. C语言程序练习-L1-017 到底有多二 (15分)
  6. EFCore3.1+编写自定义的EF.Functions扩展方法
  7. Linux下select函数的使用
  8. 七大步骤,详解预置算法构建模型的全过程
  9. 怪异的万圣节:图标素材,给对生活有仪式感的设计师!
  10. 在Java中应用函数式编程请小心!
  11. cmd 看图片十六进制_Fun Python | 女朋友让我把这网站上的图片都下载下来
  12. php中的字典数据类型,python中字典数据类型常用操作
  13. 抽奖活动mysql表设计_抽奖项目的系统设计方案
  14. TD-LTE原理及关键技术个人笔记(1)TD-LTE概述
  15. HTTP协议和APACHE
  16. 策略模式:网络小说的固定套路
  17. centos 6.4 thinly-provisioned
  18. 什么是UV贴图和展开?没有他们3D建模会变成什么样?来看看!
  19. c罗python可视化分析_梅西、内马尔谁是全能的五边形战士?教你用BI做出可视化能力图...
  20. 查看保存的服务器密码

热门文章

  1. ie兼容响应式布局的实现总结 和 针对ie浏览器的CSS
  2. PHP生成各种验证码和Ajax验证
  3. linux 下使用 curl post
  4. 部署也是工程的一部分,也要编程(自动化)
  5. 构造不可变类及其优点
  6. Myeclipse中Tomcat的两种部署方式
  7. plex实现流媒体服务器_Plex继续远离服务器,提供网络节目
  8. 搭建spring boot环境并测试一个controller
  9. JavaScript-client、offset、scroll、定时器
  10. @Springboot搭建项目controller层接收json格式的对象失败