一、泛型基础核心
1. 什么是泛型
泛型 <T>:延迟指定类型 ,编写一套通用代码,支持多种类型复用,类型安全、无装箱拆箱。
作用:
- 复用代码,不用为
int/string/自定义类写多套重载 - 编译时类型检查,避免强制转换
- 值类型无装箱拆箱,性能更高
2. 泛型基本语法
cs
// 泛型类
public class MyList<T>
{
public void Add(T item) { }
}
// 泛型方法
public static void Show<T>(T val) { }
二、泛型约束(where)
1. 为什么需要约束
不确定 T 是什么类型时,不能随便调用方法、不能 new ,约束用来限制 T 的范围,给编译器放行。
2. 5 种常用约束
-
基类约束 :必须继承某个类
cswhere T : Animal -
接口约束:必须实现某个接口
cswhere T : IComparable -
引用类型约束:必须是 class
cswhere T : class -
值类型约束:必须是 struct
cswhere T : struct -
无参构造函数约束:必须有无参构造
cswhere T : new()3. 多约束组合
cspublic class Demo<T> where T : class, IRun, new()含义:T 必须是引用类型 、实现
IRun、有无参构造。
三、协变、逆变(in /out)
- 协变
out T:子类 → 父类 隐式转换,只读、往外输出 - 逆变
in T:父类 → 子类 隐式转换,只写、往里输入
为什么会有协变逆变
没有协变逆变时:
List<Dog> 不能 赋值给 List<Animal>
泛型默认不支持类型隐式转换 ,需要 in/out 标记接口委托。
协变 out T(输出)
适用:只返回 T,不接收 T
cs
// 协变:out
public interface IRead<out T>
{
T Get();
}
使用:
cs
IRead<Dog> dogRead = ...;
IRead<Animal> aniRead = dogRead; // 协变允许:子类泛型 → 父类泛型
记忆:out 往外抛,往上转(子类转父类)
逆变 in T(输入)
适用:只接收 T,不返回 T
cs
// 逆变:in
public interface IWrite<in T>
{
void Set(T item);
}
使用:
cs
IWrite<Animal> aniWrite = ...;
IWrite<Dog> dogWrite = aniWrite; // 逆变允许:父类泛型 → 子类泛型
记忆:in 往里收,往下转(父类转子类)
常用内置例子
- 协变:
IEnumerable<out T> - 逆变:
Action<in T> - 既不协变也不逆变:
List<T>没有 in/out,不能隐式转换
关键规则
- 只能用在接口、委托,不能用在普通类
out T只能做返回值,不能做参数in T只能做方法参数,不能做返回值
四、泛型底层:CLR 模板生成机制
1. 核心原理
C# 泛型不是语法糖,是 CLR 运行时原生支持 。编译时只生成一套 IL 模板 ,运行时根据实际类型动态构造专属类型。
2. 引用类型 / 值类型 生成规则
(1)引用类型:共享同一份实现
cs
MyList<string>
MyList<Animal>
MyList<Dog>
CLR 只生成一个版本 ,所有引用类型共用同一份机器码,因为引用类型内存大小一致,都是地址。
(2)值类型:每个类型单独生成一套
cs
MyList<int>
MyList<double>
MyList<long>
每个不同值类型 ,CLR 都会单独生成一套专属机器码
因为 int/double 内存占用、布局不同,不能共享。
泛型为什么无装箱拆箱
非泛型 ArrayList 存值类型会装箱到 object;泛型 List<T> 是针对性专用类型 ,值类型直接在专属版本分配内存,无需装箱拆箱,性能高。
泛型类型在内存中是什么
运行时每一个封闭泛型类型 (如 List<int>)都是独立的全新类型,有自己的:
-
类型对象
-
方法表
-
VTable
-
静态字段(每个封闭类型静态字段互不共享)
cspublic class Test<T> { public static int Num; }Test<int>.Num和Test<string>.Num是两个独立静态变量,互不干扰。
五、总结
- 泛型 :
<T>延迟指定类型,编译时类型安全,值类型无装箱。 - 约束 where:限制 T 为基类 / 接口 / 引用 / 值类型 / 无参构造,开放语法权限。
- 协变 out T :输出型,子类泛型隐式转父类泛型;逆变 in T:输入型,父类泛型隐式转子类泛型;只能修饰接口、委托。
底层机制:
- 编译生成一份 IL 模板;
- 引用类型共享一份实现;
- 每个值类型单独生成专属版本;
- 天然避免装箱拆箱,性能优于非泛型集合。