C# 泛型:约束、协变逆变、底层模板生成机制

一、泛型基础核心

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 种常用约束
  1. 基类约束 :必须继承某个类

    cs 复制代码
    where T : Animal
  2. 接口约束:必须实现某个接口

    cs 复制代码
    where T : IComparable
  3. 引用类型约束:必须是 class

    cs 复制代码
    where T : class
  4. 值类型约束:必须是 struct

    cs 复制代码
    where T : struct
  5. 无参构造函数约束:必须有无参构造

    cs 复制代码
    where T : new()
    3. 多约束组合
    cs 复制代码
    public 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

  • 静态字段(每个封闭类型静态字段互不共享)

    cs 复制代码
    public class Test<T>
    {
        public static int Num;
    }

    Test<int>.NumTest<string>.Num 是两个独立静态变量,互不干扰。

五、总结

  • 泛型<T> 延迟指定类型,编译时类型安全,值类型无装箱。
  • 约束 where:限制 T 为基类 / 接口 / 引用 / 值类型 / 无参构造,开放语法权限。
  • 协变 out T :输出型,子类泛型隐式转父类泛型;逆变 in T:输入型,父类泛型隐式转子类泛型;只能修饰接口、委托。

底层机制

  • 编译生成一份 IL 模板
  • 引用类型共享一份实现
  • 每个值类型单独生成专属版本
  • 天然避免装箱拆箱,性能优于非泛型集合。
相关推荐
思麟呀9 小时前
在C++基础上理解CSharp-5
开发语言·c++·c#
z落落12 小时前
C#ToolStrip+StatusStrip 状态栏实时显示系统时间+NotifyIcon系统托盘
开发语言·c#
ctrl_v助手13 小时前
VisionPro (R) QuickBuild相机的连接
服务器·笔记·数码相机·c#
北域码匠14 小时前
奇偶归并排序:并行计算的排序利器
数据结构·算法·c#·排序算法
zhangfeng113315 小时前
国家超算中心 昆山站 异构加速卡1 显存16GB详细配置, 海光 Z100SM HCU
linux·网络·深度学习·c#
z落落15 小时前
C# WinForm TreeView 树形控件+ListView控件+菜单栏
开发语言·c#
ABprogramming16 小时前
Aspire入门指南
c#·.net
加号316 小时前
【C#】VS2022 传统 ASP.NET Web 服务(.asmx)接口实现指南
前端·c#·asp.net
加号31 天前
【C#】 文件与目录管理:创建、删除操作的技术解析
开发语言·c#
用户395240998801 天前
SqlSugar 连接 PostgreSQL 报错 42P01: relation does not exist 的排查与修复
c#