理解C#泛型运作原理
发布日期:2021-05-09 06:18:03 浏览次数:19 分类:博客文章

本文共 23165 字,大约阅读时间需要 77 分钟。

前言

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

一.泛型之前的故事

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

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;        }   }

然后我们来验证下:

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

输出:

ryzenreedwymengavinNow arrayStr Capacity:401234Now array Capacity:8

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

  1. 引用类型string进行了类型转换的验证
  2. 值类型int32进行了装箱和拆箱操作,同时进行类型转换类型的检验
  3. 发生的这一切都是在运行时的,假如类型转换错误,得在运行时才能报错

大致执行模型如下:

引用类型:

值类型:

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

二.用泛型实现

public class ArrayExpandable
{ 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; } } }

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

var arrayStr = new ArrayExpandable
();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
();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}");

输出:

ryzenreedwymengavinNow arrayStr Capacity:401234Now array Capacity:8

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

//声明类.class public auto ansi beforefieldinit MetaTest.ArrayExpandable`1
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
::_size IL_0007: ldarg.0 IL_0008: ldfld !0[] class MetaTest.ArrayExpandable`1
::_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
::_size IL_001c: ldc.i4.1 IL_001d: add IL_001e: call instance void class MetaTest.ArrayExpandable`1
::EnsuresCapacity(int32) IL_0023: nop IL_0024: ldarg.0 IL_0025: ldfld !0[] class MetaTest.ArrayExpandable`1
::_items IL_002a: ldarg.0 IL_002b: ldfld int32 class MetaTest.ArrayExpandable`1
::_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
::_size IL_003d: ldc.i4.1 IL_003e: add IL_003f: stfld int32 class MetaTest.ArrayExpandable`1
::_size IL_0044: ret} // end of method ArrayExpandable`1::Add

 原来定义的时候就是用了个T作为占位符,起一个模板的作用,我们对其实例化类型参数的时候,补足那个占位符,我们可以在编译期就知道了其类型,且不用在运行时进行类型检测,而我们也可以对比ArrayExpandableArrayExpandable<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<T>:

IL_007f:  newobj     instance void class GenericSample.ArrayExpandable`1
::.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
::Add(!0) IL_0093: nop IL_0094: ldloc.2 IL_0095: ldloc.s V_6 IL_0097: callvirt instance !0 class GenericSample.ArrayExpandable`1
::get_Item(int32)

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

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

[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
array = new ArrayExpandable
(); for (int i = 0; i < 10000; i++) { array.Add(i); int value = array[i]; } array = null;//确保进行垃圾回收; } [Benchmark] public void EnumAE_Gen_RefType() { ArrayExpandable
array = new ArrayExpandable
(); for (int i = 0; i < 10000; i++) { array.Add("r"); string value = array[i]; } array = null;//确保进行垃圾回收; } [Benchmark] public void EnumList_ValueType() { List
array = new List
(); for (int i = 0; i < 10000; i++) { array.Add(i); int value = array[i]; } array = null;//确保进行垃圾回收; } [Benchmark] public void EnumList_RefType() { List
array = new List
(); 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基金会牛逼

三.泛型的多态性

多态的声明

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

interface IFoo
{ void InterfaceMenthod(InterfaceT interfaceT);}class Foo
: IFoo
{ public ClassT1 Field; public delegate void MyDelegate
(DelegateT delegateT); public void DelegateMenthod
(DelegateT delegateT, MyDelegate
myDelegate) { myDelegate(delegateT); } public static string operator +(Foo
foo,string s) { return $"{s}:{foo.GetType().Name}"; } public List
Property{ get; set; } public ClassT1 Property1 { get; set; } public ClassT this[int index] => Property[index];//没判断越界 public Foo(List
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 menthodT, MenthodT1 menthodT1) { Console.WriteLine($"Method
:{(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()); }}

控制台测试代码:

static void Main(string[] args){     Test();     Console.ReadLine();}static void Test(){     var list = new List
() { 1, 2, 3, 4 }; var foo = new Foo
(list, "ryzen"); var index = 0; Console.WriteLine($"索引:索引{index}的值:{foo[index]}"); Console.WriteLine($"Filed:{foo.Field}"); foo.Method(2333); foo.Method
(DateTime.Now, 2021); foo.DelegateMenthod
("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}");}

输出如下:

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

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

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

多态的继承

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

interface IFooBase
{}interface IFoo
: IFooBase
{ void InterfaceMenthod(InterfaceT interfaceT);}class FooBase
{}class Foo
: FooBase
,IFoo
{}

我们可以通过例子看出:

  • 由于Foo的基类FooBase定义的和Foo有着共享的类型参数ClassT,因此可以在继承的时候不实例化类型
  • FooIFoo接口没定义相同的类型参数,因此可以在继承的时候实例化出接口的类型参数StringBuild出来
  • IFooIFooBase没定义相同的类型参数,因此可以在继承的时候实例化出接口的类型参数string出来
  • 上述都体现出继承的多态性

多态的递归

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

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

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

四.泛型的约束

where的约束

我们先上代码:

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

再改动下Foo类:

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

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

out和in的约束

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

协变

我们先看下代码:

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

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

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

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

  • 由于Foo继承FooBase,本身子类Foo包含着父类允许访问的成员,因此能隐式转换父类,这是类型安全的转换,因此叫协变
  • 在为泛型接口用out标识其类型参数支持协变后,约束其方法的返回值和属性的Get(本质也是个返回值的方法)才能引用所声明的类型参数,也就是作为输出值,用out很明显的突出了这一意思

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

public interface IEnumerable
: IEnumerable { new IEnumerator
GetEnumerator(); }

逆变

我们将上面代码改下:

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

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

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

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

  • 由于FooBaseFoo的父类,并不包含子类的自由的成员,转为为子类Foo是类型不安全的,因此在运行时强式转换的报错了,但编译期是不能够确认的
  • 在为泛型接口用in标识其类型参数支持逆变后,in约束其接口成员不能将其作为返回值(输出值),我们会发现协变和逆变正是一对反义词
  • 这里提一句,值类型是不支持协变和逆变的

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

public delegate void Action
(T obj);

五.泛型的反射

我们先来看看以下代码:

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

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

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

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

metainfo:

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

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

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

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

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

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

输出:

-----------lsInt---------------lsInt is generic?:TrueGeneric type:Int32---------Menthods:get_Itemset_Itemget_Capacityset_Capacityget_CountAddGetTypeToStringEqualsGetHashCode---------Properties:System.Int32:ItemSystem.Int32:CapacitySystem.Int32:Count-----------lsStr---------------lsStr is generic?:TrueGeneric type:String---------Menthods:get_Itemset_Itemget_Capacityset_Capacityget_CountAddGetTypeToStringEqualsGetHashCode---------Properties:System.String:ItemSystem.Int32:CapacitySystem.Int32:Count

六.总结

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

  • 编译期确定类型,避免值类型的拆装箱和不必要的运行时类型检验,同样运行时也能通过isas进行类型检验
  • 通过约束进行对类型参数实例化的范围
  • 同时在IL层面,实例化相同类型参数的时候共享一份本地代码
  • 由于元数据的存在,也能在运行时进行反射,增强其灵活性

参考

《CLR Via C# 第四版》

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

上一篇:C#备份一个文件到指定的文件夹里面
下一篇:WPF NET5 Prism8.0的升级指南

发表评论

最新留言

做的很好,不错不错
[***.243.131.199]2025年05月05日 21时16分01秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

Nmap渗透测试指南之防火墙/IDS逃逸、信息搜集 2023-01-23
Nmap端口服务 之 CentOS7 关于启动Apache(httpd)服务、telnet服务、smtp服务、ftp服务、sftp服务、snmp服务 2023-01-23
PHP系列:PHP 基础编程 2(时间函数、数组---实现登录&注册&修改) 2023-01-23
PHP系列:使用PHP实现登录注册功能的完整指南 2023-01-23
Python&aconda系列:cmd/powershell/anaconda prompt提示“系统找不到指定的路径”(亲测有效) 2023-01-23
Python&aconda系列:conda踩坑记录2.UnsatisfiableError: The following specifications were found to be incompa 2023-01-23
Python&aconda系列:Jupyter Notebook快速上手、深度学习库PyTorch安装 2023-01-23
Python&aconda系列:(W&L)Conda使用faiss-gpu报错及解决办法、安装numpy的坑、cmd执行Python脚本找不到第三方库、安装tensorflow-gpu时遇到的from 2023-01-23
python&anconda 系列:Pycharm在debug问题的N种解决方案(一般程序、web方向、人工智能方向) 2023-01-23
python&anconda系列(亲测有效):tensorflow AttributeError: ‘str’ object has no attribute ‘decode’ 2023-01-23
python&anconda系列:tf.keras.backend.get_session()和keras.backend.get_会话()返回不同的会话对象(待解答) 2023-01-23
"WARNING: Increasing RAM size to 1GB" and "Cannot set up guest memory 'xxx.ram': Invalid argument". 2023-01-23
#if 0 #elif 1 #else #endif 用法 2023-01-23
#include <gdiplus.h>出错 2023-01-23
$ajax({}).done 和 $ajax({}) success 区别 2023-01-23
'ascii' codec can't encode characters in position 0-4: ordinal not in range(128) 2023-01-23
(反射+内省机制的运用)处理jdbc的结果集 2023-01-23
(反射+内省机制的运用)简单模拟spring IoC容器的操作 2023-01-23
#C8# UVM中的factory机制 #S8.2.3# 重载 component 哪些情形 2023-01-23
(转)SQLServer全局变量 2023-01-23