前言#

我们都知道泛型在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}");

输出:

copy
ryzen
reed
wymen
gavin
now arraystr capacity:4
0
1
2
3
4
now array capacity:8

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

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

大致执行模型如下:

引用类型:

值类型:

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

二.用泛型实现#

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

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

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

输出:

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

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

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

三.泛型的多态性#

多态的声明#

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

interface 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());
 }
}

控制台测试代码:

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

输出如下:

构造函数: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

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

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

多态的继承#

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

interface 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,因此可以在继承的时候不实例化类型
  • 而foo和ifoo接口没定义相同的类型参数,因此可以在继承的时候实例化出接口的类型参数stringbuild出来
  • ifoo和ifoobase没定义相同的类型参数,因此可以在继承的时候实例化出接口的类型参数string出来
  • 上述都体现出继承的多态性

多态的递归#

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

 class d<t> { }
 class c<t> : d<c<c<t>>> 
 { 
 void foo()
 {
  var foo = new c<c<t>>();
  console.writeline(foo.tostring());
 }
 }

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

四.泛型的约束#

where的约束#

我们先上代码:

 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类:

class foo : foobase 
{
 public foo(string str)
 {

 }
}

static void testconstraint()
{
 var someclass = new someclass<int, foo>();//编译失败,因为new()约束必须类含有一个无参构造器,可以再给foo类加上个无参构造器就能编译通过
}

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

out和in的约束#

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

协变#

我们先看下代码:

class 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,那段代码就会编译正常,但是会引出另外一段编译报错:

interface 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也是这么定义的:

 public interface ienumerable<out t> : ienumerable
 {
  new ienumerator<t> getenumerator();
 }

逆变#

我们将上面代码改下:

class 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,发现出现另外一处编译失败

interface 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;//编译通过,运行时不报错

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

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

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

public delegate void action<in t>(t obj);

五.泛型的反射#

我们先来看看以下代码:

static 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方法:

void 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: nop
 il_0001: newobj  instance void class metatest.arrayexpandable`1/*02000003*/<int32>/*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*/<int32>/*1b000001*/::add(!0) /* 0a00000d */
 il_000e: nop
 il_000f: newobj  instance void class metatest.arrayexpandable`1/*02000003*/<string>/*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*/<string>/*1b000002*/::add(!0) /* 0a00000f */
 il_0020: nop
 il_0021: newobj  instance void class metatest.arrayexpandable`1/*02000003*/<string>/*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*/<string>/*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!0

typespec #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>定义一份定义表,生成两份规格,也就是当你实例化类型参数为int和string的时候,分别生成了两份规格代码,同时还发现以下的现象:

var lsint = new arrayexpandable<int>();//引用的是类型规格1b000001的成员0a00000c .ctor构造
lsint.add(1);//引用的是类型规格1b000001的成员0a00000d add
 
var lsstr = new arrayexpandable<string>();//引用的是类型规格1b000002的成员0a00000e .ctor构造
lsstr.add("ryzen");//引用的是类型规格1b000002的成员0a00000f add
var lsstr1 = new arrayexpandable<string>();//和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?: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体系中一个很重要的编程思想,主要有以下亮点:

  • 编译期确定类型,避免值类型的拆装箱和不必要的运行时类型检验,同样运行时也能通过is和as进行类型检验
  • 通过约束进行对类型参数实例化的范围
  • 同时在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#泛型运作原理的文章就介绍到这了,更多相关c#泛型运作原理内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!