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 模板
  • 引用类型共享一份实现
  • 每个值类型单独生成专属版本
  • 天然避免装箱拆箱,性能优于非泛型集合。
相关推荐
bestcxx2 小时前
多个维度对 Java、Python、C#、Go 这四种主流编程语言进行比较
java·python·c#
我是唐青枫2 小时前
内存为什么越来越高?C#.NET GC 详解:分代回收、LOH、终结器与性能优化实战
性能优化·c#·.net
游乐码3 小时前
c#反射笔记二
笔记·c#
工程师0073 小时前
C# 变量:生命周期、作用域、变量逃逸
c#·生命周期·作用域·逃逸
游乐码3 小时前
c#反射笔记(一)
c#
江沉晚呤时4 小时前
C# 运行时类型创建:深入探索动态类型生成技术
开发语言·c#
唐青枫4 小时前
别再把 Redis 当黑盒了!C#.NET IDistributedCache 详解:官方分布式缓存接口从入门到实战
c#·.net
Bofu-4 小时前
【音频测试】03-WPF 实现声道自动验证 + Whisper 语音识别录音检测
c#·whisper·wpf·音视频·音频测试·naudio 声道控制
游乐码5 小时前
c#特性笔记
笔记·c#