Skip to content
On this page

深入理解泛型

对于泛型的理解 我是直接跳到对泛型集合的使用上 用着用着大概就明白了这大概是个什么东西。
至于为什么要使用泛型,什么情况下定义属于自己的泛型,定义泛型又能为程序带来哪些好处 ,泛型的工作原理 其实当时思考的很少

1.什么是泛型

泛型是实现面向切面编程的一种实现方式。

2. C#泛型的优点

  1. 源代码的保护
  2. 类型安全
  3. 更清晰的代码
  4. 更佳的性能

3. C#泛型的工作原理

1. 开放类型和封闭类型

具有泛型类型的参数被称为 开放类型。
为所有的类型参数传递了实际的的数据类型的称为 封闭类型。

CLR禁止构造任何开放类型的实例。

avatar

JIT编译的代码如果包含了泛型的内容,那么它会根据泛型类型的消费者指定的类型参数,将CIL中泛型代码中的占位符T替换为一个具体的类型,从而明确当前执行的泛型代码是针对哪个类型来使用的,其中替换的过程是由CLR在运行时进行主导,JIT来实际操作完成的。这个在运行时确认了类型的泛型又被称之为“封闭类型”,反之在运行时确认之前的泛型称为“开放类型”。

2. 代码爆炸

CLR会为每种不同的方法/类型组合 生成本机的代码。这可能造成应用程序的工作集显著增大,从而降低性能。

CLR 内建了一些优化措施缓解代码爆炸。

引用类型:

ex:

csharp
List<T>
List<int>  
List<string> 
List<long>

avatar

C# 所有的 类 在其上都有一个 MethodTable 类来承载,所以它就是鉴别我们是否生成多个个体的依据

方法表指针不同 => CLR层面类的承载不同 => 代码区占用的内存容量爆炸

avatar
JIT层面 对所有引用类型做了封装处理 公用一张方法表 统一为Canon类型

基于:所有引用类型的实参或者变量实际只是指向堆上对象的指针。 而 值类型是不能才去同样的措施的 原因类似于 long++ 和 int++ 因为在内存中所占用的字节数不同 进行方法操作的时候移动的内存位置也不同。

此外: 在不同的程序集中 加入为特定的类型实参调用了一个方法,那么以后再用到相同的类型实参调用此方法,CLR只会编译一次代码。

3. 泛型约束

avatar

4. 协变,逆变,不变

在C#中 协变与逆变能够实现数组类型,委托类型,泛型类型参数的隐式引用转换。协变保留分配兼容性,逆变则与之相反。

在里氏替换原则中存在 一个定义 返回值和异常类型 需要保持不变或者变得跟具体。参数类型需要满足相反的变化。

avatar

avatar

上图演示了某方法需要接收熊科类作为方法参数 输出类型为高精度浮点型的方法例子。
如果定义了一个泛型委托且满足协变与逆变。那么将方法传递给这个委托后可以安全的转换为传参为哺乳类类型输出类型为高精度整形。
C#提供了两个关键字 out 表示协变 in 表示逆变。如: Func 与 Action。

如果A 继承自 B   
不变: F(A) 与 F(B) 没有继承关系   
协变: F(A) 继承自F(B)   
逆变: F(B) 继承自F(A)

4. C#、C++、Java 泛型对比

不同的编译器对于泛型的处理方式是不同的。通常情况下,一个编译器处理泛型的方式主要有两种:Code specialization和Code sharing。

Code specialization:在编译时根据泛型生成不同的Code。
Code sharing:所有泛型共享同一Code,通过类型检查、类型擦除、类型转换实现。

  • C++
    采用了Code Specification技术。即:在编译期对泛型类型(模板类、模板函数)进行特化,根据不同的模板实参为每个泛型类型的不同的实例化版本静态地生成一个强类型。例如,m个泛型类型,n个不同的模板实参,编译后将产生m ×n个强类型。

    • 优点:灵活性强,很高的性能以及极强的扩展能力

    • 缺点:代码膨胀,过分特化导致Name Mangling泛滥,给调试造成了不便。而且不同物理实现之间不能共享实例化的强类型。

  • Java
    采用了Code Sharing技术。即:对泛型类型(模板类、模板函数)进行实例化,所有的实例化类型共享同一套强类型的代码。如,List<int>List<string>共享同一套强类型的代码List<Object>

    • 优点:代码简洁、轻便。

    • 缺点:操作效率很低,不够灵活。

  • C#
    综合了C++的Code Specification和Java的Code Sharing技术。采用了两阶段延迟特化,即:

  1. 从C#源码到IL Assembly的静态编译过程。此阶段基于Code Sharing的思想,只生成了一套抽象的IL代码。

  2. 在CLR运行时JIT编译过程中,将IL代码即时编译成本地代码的特化过程。此阶段基于Code Specification的思想,根据每个特化实参生成一个特化的强类型。

    • 优点:兼备Code Specialization和Code Sharing的优点;Assembly之间能够实现泛型代码的相互引用和复用;代码简洁

Date: 2022/12/16

Authors: 周志豪

Tags: .NET、Generics