C#泛型运作原理的深入理解
前言#
我们都知道泛型在C#的重要性,泛型是OOP语言中三大特征的多态的最重要的体现,几乎泛型撑起了整个.NET框架,在讲泛型之前,我们可以抛出一个问题,我们现在需要一个可扩容的数组类,且满足所有类型,不管是值类型还是引用类型,那么在没有用泛型方法实现,如何实现?
一.泛型之前的故事#
我们肯定会想到用object来作为类型参数,因为在C#中,所有类型都是基于Object类型的。因此Object是所有类型的最基类,那么我们的可扩容数组类如下:
publicclassArrayExpandable
{
privateobject?[]_items=null;
privateint_defaultCapacity=4;
privateint_size;
publicobject?this[intindex]
{
get
{
if(index<0||index>=_size)
thrownewArgumentOutOfRangeException(nameof(index));
return_items[index];
}
set
{
if(index<0||index>=_size)
thrownewArgumentOutOfRangeException(nameof(index));
_items[index]=value;
}
}
publicintCapacity
{
get=>_items.Length;
set
{
if(value<_size)
{
thrownewArgumentOutOfRangeException(nameof(value));
}
if(value!=_items.Length)
{
if(value>0)
{
object[]newItems=newobject[value];
if(_size>0)
{
Array.Copy(_items,newItems,_size);
}
_items=newItems;
}
else
{
_items=newobject[_defaultCapacity];
}
}
}
}
publicintCount=>_size;
publicArrayExpandable()
{
_items=newobject?[0];
}
publicArrayExpandable(intcapacity)
{
_items=newobject?[capacity];
}
publicvoidAdd(object?value)
{
//数组元素为0或者数组元素容量满
if(_size==_items.Length)EnsuresCapacity(_size+1);
_items[_size]=value;
_size++;
}
privatevoidEnsuresCapacity(intsize)
{
if(_items.Length
然后我们来验证下:
vararrayStr=newArrayExpandable();
varstrs=newstring[]{"ryzen","reed","wymen"};
for(inti=0;i
输出:
Copy
ryzen
reed
wymen
gavin
NowarrayStrCapacity:4
0
1
2
3
4
NowarrayCapacity:8
貌似输出结果是正确的,能够动态进行扩容,同样的支持值类型Struct的int32和引用类型的字符串,但是其实这里会发现一些问题,那就是
- 引用类型string进行了类型转换的验证
 
- 值类型int32进行了装箱和拆箱操作,同时进行类型转换类型的检验
 
- 发生的这一切都是在运行时的,假如类型转换错误,得在运行时才能报错
 
大致执行模型如下:
引用类型:
 
值类型:
 
 那么有没有一种方法能够避免上面遇到的三种问题呢?在借鉴了cpp的模板和java的泛型经验,在C#2.0的时候推出了更适合.NET体系下的泛型
二.用泛型实现#
publicclassArrayExpandable
{
privateT[]_items;
privateint_defaultCapacity=4;
privateint_size;
publicTthis[intindex]
{
get
{
if(index<0||index>=_size)
thrownewArgumentOutOfRangeException(nameof(index));
return_items[index];
}
set
{
if(index<0||index>=_size)
thrownewArgumentOutOfRangeException(nameof(index));
_items[index]=value;
}
}
publicintCapacity
{
get=>_items.Length;
set
{
if(value<_size)
{
thrownewArgumentOutOfRangeException(nameof(value));
}
if(value!=_items.Length)
{
if(value>0)
{
T[]newItems=newT[value];
if(_size>0)
{
Array.Copy(_items,newItems,_size);
}
_items=newItems;
}
else
{
_items=newT[_defaultCapacity];
}
}
}
}
publicintCount=>_size;
publicArrayExpandable()
{
_items=newT[0];
}
publicArrayExpandable(intcapacity)
{
_items=newT[capacity];
}
publicvoidAdd(Tvalue)
{
//数组元素为0或者数组元素容量满
if(_size==_items.Length)EnsuresCapacity(_size+1);
_items[_size]=value;
_size++;
}
privatevoidEnsuresCapacity(intsize)
{
if(_items.Length
那么测试代码则改写为如下:
vararrayStr=newArrayExpandable();
varstrs=newstring[]{"ryzen","reed","wymen","gavin"};
for(inti=0;i();
for(inti=0;i<5;i++)
{
array.Add(i);
intvalue=array[i];
Console.WriteLine(value);
}
Console.WriteLine($"Now{nameof(array)}Capacity:{array.Capacity}");  
输出:
Copy
ryzen
reed
wymen
gavin
NowarrayStrCapacity:4
0
1
2
3
4
NowarrayCapacity:8
我们通过截取部分ArrayExpandable的IL查看其本质是个啥: 
//声明类
.classpublicautoansibeforefieldinitMetaTest.ArrayExpandable`1
extends[System.Runtime]System.Object
{
.custominstancevoid[System.Runtime]System.Reflection.DefaultMemberAttribute::.ctor(string)=(0100044974656D0000)
}
//Add方法
.methodpublichidebysiginstancevoidAdd(!T'value')cilmanaged
{
//代码大小69(0x45)
.maxstack3
.localsinit(boolV_0)
IL_0000:nop
IL_0001:ldarg.0
IL_0002:ldfldint32classMetaTest.ArrayExpandable`1::_size
IL_0007:ldarg.0
IL_0008:ldfld!0[]classMetaTest.ArrayExpandable`1::_items
IL_000d:ldlen
IL_000e:conv.i4
IL_000f:ceq
IL_0011:stloc.0
IL_0012:ldloc.0
IL_0013:brfalse.sIL_0024
IL_0015:ldarg.0
IL_0016:ldarg.0
IL_0017:ldfldint32classMetaTest.ArrayExpandable`1::_size
IL_001c:ldc.i4.1
IL_001d:add
IL_001e:callinstancevoidclassMetaTest.ArrayExpandable`1::EnsuresCapacity(int32)
IL_0023:nop
IL_0024:ldarg.0
IL_0025:ldfld!0[]classMetaTest.ArrayExpandable`1::_items
IL_002a:ldarg.0
IL_002b:ldfldint32classMetaTest.ArrayExpandable`1::_size
IL_0030:ldarg.1
IL_0031:stelem!T
IL_0036:ldarg.0
IL_0037:ldarg.0
IL_0038:ldfldint32classMetaTest.ArrayExpandable`1::_size
IL_003d:ldc.i4.1
IL_003e:add
IL_003f:stfldint32classMetaTest.ArrayExpandable`1::_size
IL_0044:ret
}//endofmethodArrayExpandable`1::Add
 
 原来定义的时候就是用了个T作为占位符,起一个模板的作用,我们对其实例化类型参数的时候,补足那个占位符,我们可以在编译期就知道了其类型,且不用在运行时进行类型检测,而我们也可以对比ArrayExpandable和ArrayExpandable在类型为值类型中的IL,查看是否进行拆箱和装箱操作,以下为IL截取部分: 
ArrayExpandable:
IL_0084:newobjinstancevoidGenericSample.ArrayExpandable::.ctor()
IL_0089:stloc.2
IL_008a:ldc.i4.0
IL_008b:stloc.sV_6
IL_008d:br.sIL_00bc
IL_008f:nop
IL_0090:ldloc.2
IL_0091:ldloc.sV_6
IL_0093:box[System.Runtime]System.Int32//box为装箱操作
IL_0098:callvirtinstancevoidGenericSample.ArrayExpandable::Add(object)
IL_009d:nop
IL_009e:ldloc.2
IL_009f:ldloc.sV_6
IL_00a1:callvirtinstanceobjectGenericSample.ArrayExpandable::get_Item(int32)
IL_00a6:unbox.any[System.Runtime]System.Int32//unbox为拆箱操作
ArrayExpandable:
IL_007f:newobjinstancevoidclassGenericSample.ArrayExpandable`1::.ctor()
IL_0084:stloc.2
IL_0085:ldc.i4.0
IL_0086:stloc.sV_6
IL_0088:br.sIL_00ad
IL_008a:nop
IL_008b:ldloc.2
IL_008c:ldloc.sV_6
IL_008e:callvirtinstancevoidclassGenericSample.ArrayExpandable`1::Add(!0)
IL_0093:nop
IL_0094:ldloc.2
IL_0095:ldloc.sV_6
IL_0097:callvirtinstance!0classGenericSample.ArrayExpandable`1::get_Item(int32)   
 我们从IL也能看的出来,ArrayExpandable的T作为一个类型参数,在编译后在IL已经确定了其类型,因此当然也就不存在装拆箱的情况,在编译期的时候IDE能够检测类型,因此也就不用在运行时进行类型检测,但并不代表不能通过运行时检测类型(可通过is和as),还能通过反射体现出泛型的灵活性,后面会讲到 
 其实有了解ArrayList和List的朋友就知道,ArrayExpandable和ArrayExpandable其实现大致就是和它们一样,只是简化了很多的版本,我们这里可以通过BenchmarkDotNet来测试其性能对比,代码如下: 
[SimpleJob(RuntimeMoniker.NetCoreApp31,baseline:true)]
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
[MemoryDiagnoser]
publicclassTestClass
{
[Benchmark]
publicvoidEnumAE_ValueType()
{
ArrayExpandablearray=newArrayExpandable();
for(inti=0;i<10000;i++)
{
array.Add(i);//装箱
intvalue=(int)array[i];//拆箱
}
array=null;//确保进行垃圾回收
}
[Benchmark]
publicvoidEnumAE_RefType()
{
ArrayExpandablearray=newArrayExpandable();
for(inti=0;i<10000;i++)
{
array.Add("r");
stringvalue=(string)array[i];
}
array=null;//确保进行垃圾回收
}
[Benchmark]
publicvoidEnumAE_Gen_ValueType()
{
ArrayExpandablearray=newArrayExpandable();
for(inti=0;i<10000;i++)
{
array.Add(i);
intvalue=array[i];
}
array=null;//确保进行垃圾回收;
}
[Benchmark]
publicvoidEnumAE_Gen_RefType()
{
ArrayExpandablearray=newArrayExpandable();
for(inti=0;i<10000;i++)
{
array.Add("r");
stringvalue=array[i];
}
array=null;//确保进行垃圾回收;
}
[Benchmark]
publicvoidEnumList_ValueType()
{
Listarray=newList();
for(inti=0;i<10000;i++)
{
array.Add(i);
intvalue=array[i];
}
array=null;//确保进行垃圾回收;
}
[Benchmark]
publicvoidEnumList_RefType()
{
Listarray=newList();
for(inti=0;i<10000;i++)
{
array.Add("r");
stringvalue=array[i];
}
array=null;//确保进行垃圾回收;
}
[Benchmark(Baseline=true)]
publicvoidEnumAraayList_valueType()
{
ArrayListarray=newArrayList();
for(inti=0;i<10000;i++)
{
array.Add(i);
intvalue=(int)array[i];
}
array=null;//确保进行垃圾回收;
}
[Benchmark]
publicvoidEnumAraayList_RefType()
{
ArrayListarray=newArrayList();
for(inti=0;i<10000;i++)
{
array.Add("r");
stringvalue=(string)array[i];
}
array=null;//确保进行垃圾回收;
}
}        
 我还加入了.NETCore3.1和.NET5的对比,且以.NETCore3.1的EnumAraayList_valueType方法为基准,性能测试结果如下:
 
用更直观的柱形图来呈现:
 
 我们能看到在这里List的性能在引用类型和值类型中都是所以当中是最好的,不管是执行时间、GC次数,分配的内存空间大小,都是最优的,同时.NET5在几乎所有的方法中性能都是优于.NETCore3.1,这里还提一句,我实现的ArrayExpandable和ArrayExpandable性能都差于ArrayList和List,我还没实现IList和各种方法,只能说句dotnet基金会牛逼 
三.泛型的多态性#
多态的声明#
类、结构、接口、方法、和委托可以声明一个或者多个类型参数,我们直接看代码:
interfaceIFoo
{
voidInterfaceMenthod(InterfaceTinterfaceT);
}
classFoo:IFoo
{
publicClassT1Field;
publicdelegatevoidMyDelegate(DelegateTdelegateT);
publicvoidDelegateMenthod(DelegateTdelegateT,MyDelegatemyDelegate)
{
myDelegate(delegateT);
}
publicstaticstringoperator+(Foofoo,strings)
{
return$"{s}:{foo.GetType().Name}";
}
publicListProperty{get;set;}
publicClassT1Property1{get;set;}
publicClassTthis[intindex]=>Property[index];//没判断越界
publicFoo(ListclassT,ClassT1classT1)
{
Property=classT;
Property1=classT1;
Field=classT1;
Console.WriteLine($"构造函数:parameter1type:{Property.GetType().Name},parameter2type:{Property1.GetType().Name}");
}
//方法声明了多个新的类型参数
publicvoidMethod(MenthodTmenthodT,MenthodT1menthodT1)
{
Console.WriteLine($"Method:{(menthodT.GetType().Name)}:{menthodT.ToString()},"+
$"{menthodT1.GetType().Name}:{menthodT1.ToString()}");
}
publicvoidMethod(ClassTclassT)
{
Console.WriteLine($"{nameof(Method)}:{classT.GetType().Name}:classT?.ToString()");
}
publicvoidInterfaceMenthod(StringBuilderinterfaceT)
{
Console.WriteLine(interfaceT.ToString());
}
}           
控制台测试代码:
staticvoidMain(string[]args)
{
Test();
Console.ReadLine();
}
staticvoidTest()
{
varlist=newList(){1,2,3,4};
varfoo=newFoo(list,"ryzen");
varindex=0;
Console.WriteLine($"索引:索引{index}的值:{foo[index]}");
Console.WriteLine($"Filed:{foo.Field}");
foo.Method(2333);
foo.Method(DateTime.Now,2021);
foo.DelegateMenthod("thisisadelegate",DelegateMenthod);
foo.InterfaceMenthod(newStringBuilder().Append("InterfaceMenthod:thisisainterfaceMthod"));
Console.WriteLine(foo+"重载+运算符");
}
staticvoidDelegateMenthod(stringstr)
{
Console.WriteLine($"{nameof(DelegateMenthod)}:{str}");
}
    
输出如下:
构造函数:parameter1type:List`1,parameter2type:String
索引:索引0的值:1
Filed:ryzen
Method:Int32:classT?.ToString()
Method:DateTime:2021/03/0211:45:40,Int64:2021
DelegateMenthod:thisisadelegate
InterfaceMenthod:thisisainterfaceMthod
重载+运算符:Foo`2 
我们通过例子可以看到的是:
- 类(结构也可以),接口,委托,方法都可以声明一个或多个类型参数,体现了声明的多态性
 
- 类的函数成员:属性,字段,索引,构造器,运算符只能引入类声明的类型参数,不能够声明,唯有方法这一函数成员具备声明和引用类型参数两种功能,由于具备声明功能,因此可以声明和委托一样的类型参数并且引用它,这也体现了方法的多态性
 
多态的继承#
父类和实现类或接口的接口都可以是实例化类型,直接看代码:
interfaceIFooBase{}
interfaceIFoo:IFooBase
{
voidInterfaceMenthod(InterfaceTinterfaceT);
}
classFooBase
{
}
classFoo:FooBase,IFoo{}
       
我们可以通过例子看出:
- 由于Foo的基类FooBase定义的和Foo有着共享的类型参数ClassT,因此可以在继承的时候不实例化类型
 
- 而Foo和IFoo接口没定义相同的类型参数,因此可以在继承的时候实例化出接口的类型参数StringBuild出来
 
- IFoo和IFooBase没定义相同的类型参数,因此可以在继承的时候实例化出接口的类型参数string出来
 
- 上述都体现出继承的多态性
 
多态的递归#
我们定义如下一个类和一个方法,且不会报错:
classD{}
classC:D>>
{
voidFoo()
{
varfoo=newC>();
Console.WriteLine(foo.ToString());
}
}    
因为T能在实例化的时候确定其类型,因此也支持这种循环套用自己的类和方法的定义
四.泛型的约束#
where的约束#
我们先上代码:
classFooBase{}
classFoo:FooBase
{
}
classsomeClasswhereT:structwhereK:FooBase,new()
{
}
staticvoidTestConstraint()
{
varsomeClass=newsomeClass();//通过编译
//varsomeClass=newsomeClass();//编译失败,string不是struct类型
//varsomeClass=newsomeClass();//编译失败,long不是FooBase类型
}
    
再改动下Foo类:
classFoo:FooBase
{
publicFoo(stringstr)
{
}
}
staticvoidTestConstraint()
{
varsomeClass=newsomeClass();//编译失败,因为new()约束必须类含有一个无参构造器,可以再给Foo类加上个无参构造器就能编译通过
}
 
 我们可以看到,通过where语句,可以对类型参数进行约束,而且一个类型参数支持多个约束条件(例如K),使其在实例化类型参数的时候,必须按照约束的条件对应实例符合条件的类型,而where条件约束的作用就是起在编译期约束类型参数的作用
out和in的约束#
 说到out和in之前,我们可以说下协变和逆变,在C#中,只有泛型接口和泛型委托可以支持协变和逆变
协变#
我们先看下代码:
classFooBase{}
classFoo:FooBase
{
}
interfaceIBar
{
TGetValue(Tt);
}
classBar:IBar
{
publicTGetValue(Tt)
{
returnt;
}
}
staticvoidTest()
{
varfoo=newFoo();
FooBasefooBase=foo;//编译成功
IBarbar=newBar();
IBarbar1=bar;//编译失败
}      
 这时候你可能会有点奇怪,为啥那段代码会编译失败,明明Foo类可以隐式转为FooBase,但作为泛型接口类型参数实例化却并不能呢?使用out约束泛型接口IBar的T,那段代码就会编译正常,但是会引出另外一段编译报错:
interfaceIBar
{
TGetValue(stringstr);//编译成功
//TGetValue(Tt);//编译失败T不能作为形参输入,用out约束T支持协变,T可以作为返回值输出
}
IBarbar=newBar();
IBarbar1=bar;//编译正常    
因此我们可以得出以下结论:
- 由于Foo继承FooBase,本身子类Foo包含着父类允许访问的成员,因此能隐式转换父类,这是类型安全的转换,因此叫协变
 
- 在为泛型接口用out标识其类型参数支持协变后,约束其方法的返回值和属性的Get(本质也是个返回值的方法)才能引用所声明的类型参数,也就是作为输出值,用out很明显的突出了这一意思
 
而支持迭代的泛型接口IEnumerable也是这么定义的:
publicinterfaceIEnumerable:IEnumerable
{
newIEnumeratorGetEnumerator();
}  
逆变#
我们将上面代码改下:
classFooBase{}
classFoo:FooBase
{
}
interfaceIBar
{
TGetValue(Tt);
}
classBar:IBar
{
publicTGetValue(Tt)
{
returnt;
}
}
staticvoidTest1()
{
varfooBase=newFooBase();
Foofoo=(Foo)fooBase;//编译通过,运行时报错
IBarbar=newBar();
IBarbar1=(IBar)bar;//编译通过,运行时报错
}       
我们再改动下IBar,发现出现另外一处编译失败
interfaceIBar
{
voidGetValue(Tt);//编译成功
//TGetValue(Tt);//编译失败T不能作为返回值输出,用in约束T支持逆变,T可以作为返回值输出
}
IBarbar=newBar();
IBarbar1=(IBar)bar;//编译通过,运行时不报错
IBarbar1=bar;//编译通过,运行时不报错      
因此我们可以得出以下结论:
- 由于FooBase是Foo的父类,并不包含子类的自由的成员,转为为子类Foo是类型不安全的,因此在运行时强式转换的报错了,但编译期是不能够确认的
 
- 在为泛型接口用in标识其类型参数支持逆变后,in约束其接口成员不能将其作为返回值(输出值),我们会发现协变和逆变正是一对反义词
 
- 这里提一句,值类型是不支持协变和逆变的
 
同样的泛型委托Action就是个逆变的例子:
publicdelegatevoidAction(Tobj); 
五.泛型的反射#
我们先来看看以下代码:
staticvoidMain(string[]args)
{
varlsInt=newArrayExpandable();
lsInt.Add(1);
varlsStr=newArrayExpandable();
lsStr.Add("ryzen");
varlsStr1=newArrayExpandable();
lsStr.Add("ryzen");
}   
然后通过ildasm查看其IL,开启视图-》显示标记值,查看Main方法:
voidMain(string[]args)cilmanaged
{
.entrypoint
//代码大小52(0x34)
.maxstack2
.locals/*11000001*/init(classMetaTest.ArrayExpandable`1/*02000003*/V_0,
classMetaTest.ArrayExpandable`1/*02000003*/V_1,
classMetaTest.ArrayExpandable`1/*02000003*/V_2)
IL_0000:nop
IL_0001:newobjinstancevoidclassMetaTest.ArrayExpandable`1/*02000003*//*1B000001*/::.ctor()/*0A00000C*/
IL_0006:stloc.0
IL_0007:ldloc.0
IL_0008:ldc.i4.1
IL_0009:callvirtinstancevoidclassMetaTest.ArrayExpandable`1/*02000003*//*1B000001*/::Add(!0)/*0A00000D*/
IL_000e:nop
IL_000f:newobjinstancevoidclassMetaTest.ArrayExpandable`1/*02000003*//*1B000002*/::.ctor()/*0A00000E*/
IL_0014:stloc.1
IL_0015:ldloc.1
IL_0016:ldstr"ryzen"/*70000001*/
IL_001b:callvirtinstancevoidclassMetaTest.ArrayExpandable`1/*02000003*//*1B000002*/::Add(!0)/*0A00000F*/
IL_0020:nop
IL_0021:newobjinstancevoidclassMetaTest.ArrayExpandable`1/*02000003*//*1B000002*/::.ctor()/*0A00000E*/
IL_0026:stloc.2
IL_0027:ldloc.1
IL_0028:ldstr"ryzen"/*70000001*/
IL_002d:callvirtinstancevoidclassMetaTest.ArrayExpandable`1/*02000003*//*1B000002*/::Add(!0)/*0A00000F*/
IL_0032:nop
IL_0033:ret
}//endofmethodProgram::Main         
打开元数据表将上面所涉及到的元数据定义表和类型规格表列出:
metainfo:
-----------定义部分
TypeDef#2(02000003)
-------------------------------------------------------
	TypDefName:MetaTest.ArrayExpandable`1(02000003)
	Flags:[Public][AutoLayout][Class][AnsiClass][BeforeFieldInit](00100001)
	Extends:0100000C[TypeRef]System.Object
	1GenericParameters
		(0)GenericParamToken:(2a000001)Name:Tflags:00000000Owner:02000003
	
	Method#8(0600000a)
	-------------------------------------------------------
		MethodName:Add(0600000A)
		Flags:[Public][HideBySig][ReuseSlot](00000086)
		RVA:0x000021f4
		ImplFlags:[IL][Managed](00000000)
		CallCnvntn:[DEFAULT]
		hasThis
		ReturnType:Void
		1Arguments
			Argument#1:Var!0
		1Parameters
		(1)ParamToken:(08000007)Name:valueflags:[none](00000000)
		
------类型规格部分
TypeSpec#1(1b000001)
-------------------------------------------------------
	TypeSpec:GenericInstClassMetaTest.ArrayExpandable`1//14代表int32
	MemberRef#1(0a00000c)
	-------------------------------------------------------
		Member:(0a00000c).ctor:
		CallCnvntn:[DEFAULT]
		hasThis
		ReturnType:Void
		Noarguments.
	MemberRef#2(0a00000d)
	-------------------------------------------------------
		Member:(0a00000d)Add:
		CallCnvntn:[DEFAULT]
		hasThis
		ReturnType:Void
		1Arguments
			Argument#1:Var!0
TypeSpec#2(1b000002)
-------------------------------------------------------
	TypeSpec:GenericInstClassMetaTest.ArrayExpandable`1
	MemberRef#1(0a00000e)
	-------------------------------------------------------
		Member:(0a00000e).ctor:
		CallCnvntn:[DEFAULT]
		hasThis
		ReturnType:Void
		Noarguments.
	MemberRef#2(0a00000f)
	-------------------------------------------------------
		Member:(0a00000f)Add:
		CallCnvntn:[DEFAULT]
		hasThis
		ReturnType:Void
		1Arguments
		Argument#1:Var!0  
 这时候我们就可以看出,元数据为泛型类ArrayExpandable定义一份定义表,生成两份规格,也就是当你实例化类型参数为int和string的时候,分别生成了两份规格代码,同时还发现以下的现象: 
varlsInt=newArrayExpandable();//引用的是类型规格1b000001的成员0a00000c.ctor构造
lsInt.Add(1);//引用的是类型规格1b000001的成员0a00000dAdd
varlsStr=newArrayExpandable();//引用的是类型规格1b000002的成员0a00000e.ctor构造
lsStr.Add("ryzen");//引用的是类型规格1b000002的成员0a00000fAdd
varlsStr1=newArrayExpandable();//和lsStr一样
lsStr.Add("ryzen");//和lsStr一样
   
 非常妙的是,当你实例化两个一样的类型参数string,是共享一份类型规格的,也就是同享一份本地代码,因此上面的代码在线程堆栈和托管堆的大致是这样的:
 
由于泛型也有元数据的存在,因此可以对其做反射:
Console.WriteLine($"-----------{nameof(lsInt)}---------------");
Console.WriteLine($"{nameof(lsInt)}isgeneric?:{lsInt.GetType().IsGenericType}");
Console.WriteLine($"Generictype:{lsInt.GetType().GetGenericArguments()[0].Name}");
Console.WriteLine("---------Menthods:");
foreach(varmethodinlsInt.GetType().GetMethods())
{
Console.WriteLine(method.Name);
}
Console.WriteLine("---------Properties:");
foreach(varpropertyinlsInt.GetType().GetProperties())
{
Console.WriteLine($"{property.PropertyType.ToString()}:{property.Name}");
}
Console.WriteLine($"\n-----------{nameof(lsStr)}---------------");
Console.WriteLine($"{nameof(lsStr)}isgeneric?:{lsStr.GetType().IsGenericType}");
Console.WriteLine($"Generictype:{lsStr.GetType().GetGenericArguments()[0].Name}");
Console.WriteLine("---------Menthods:");
foreach(varmethodinlsStr.GetType().GetMethods())
{
Console.WriteLine(method.Name);
}
Console.WriteLine("---------Properties:");
foreach(varpropertyinlsStr.GetType().GetProperties())
{
Console.WriteLine($"{property.PropertyType.ToString()}:{property.Name}");
}
输出:
-----------lsInt---------------
lsIntisgeneric?:True
Generictype: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---------------
lsStrisgeneric?:True
Generictype: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体系中一个很重要的编程思想,主要有以下亮点:
- 编译期确定类型,避免值类型的拆装箱和不必要的运行时类型检验,同样运行时也能通过is和as进行类型检验
 
- 通过约束进行对类型参数实例化的范围
 
- 同时在IL层面,实例化相同类型参数的时候共享一份本地代码
 
- 由于元数据的存在,也能在运行时进行反射,增强其灵活性
 
参考#
DesignandImplementationofGenericsforthe.NETCommonLanguageRuntime
https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/
《CLRViaC#第四版》
《你必须知道的.NET(第二版)》
到此这篇关于C#泛型运作原理的文章就介绍到这了,更多相关C#泛型运作原理内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。