C# 泛型协变与逆变

一、基础:普通类的父子转换(铺垫)

1. 隐式转换(向上转型)

子类对象可以直接赋值给父类变量(隐式转换、安全)

cs 复制代码
Bird bird = new Bird();      
Sparrow sp = new Sparrow();

sp = new Sparrow();
bird = sp;   // 隐式转换:子类 → 父类(小范围 → 大范围,安全)

2. 显式强制转换(向下转型)

父类对象不能直接赋值给子类变量,必须强制转换

cs 复制代码
// sp = bird; // 直接报错:父类不能隐式转子类
sp = (Sparrow)bird; // 强制转换:父类 → 子类

总结

子类 → 父类:隐式转换(天然支持)

父类 → 子类:必须强制转换


二、核心难点:List<T> 为什么不支持转换?

代码报错原因(必考)

cs 复制代码
List<Bird> birds = new List<Bird>();
List<Sparrow> sparrows = new List<Sparrow>();

// birds = sparrows; 【编译报错】

原理

Bird 和 Sparrow 是父子关系,但是 List<Bird> 和 List<Sparrow> 没有任何继承关系!

List 是普通泛型类,不具备协变/逆变能力,所以无法互相赋值。


三、协变(out T)------ 子类泛型 → 父类泛型

1. 协变关键字:out

2. 协变规则(三条铁律)

  • 转换方向:子类泛型 → 父类泛型(正向、向上)

  • 只能用于:泛型接口 / 泛型委托

  • 语法:泛型参数前加 out T

3. 协变经典案例(IEnumerable<out T>)

cs 复制代码
// 支持协变:子类集合 → 父类集合
IEnumerable<Bird> birds1 = new List<Sparrow>();

原因:IEnumerable<out T> 接口 T 带 out 协变标识。

4. 委托协变案例(Func 返回值协变)

cs 复制代码
// Func<in T, out TResult> 返回值支持协变
Func<int, Sparrow> f1 = a => new Sparrow();
Func<int, Bird> f2 = f1; 
// 协变:Sparrow(子类) 返回值 → Bird(父类) 返回值

5. 常见支持协变的系统接口/委托

  • IEnumerable<out T>

  • IReadOnlyList<out T>

  • IReadOnlyCollection<out T>

  • Func<out TResult>


四、逆变(in T)------ 父类泛型 → 子类泛型

1. 逆变关键字:in

2. 逆变规则(三条铁律)

  • 转换方向:父类泛型 → 子类泛型(反向、向下)

  • 只能用于:泛型接口 / 泛型委托

  • 作用于:方法输入参数

3. 逆变全套案例

cs 复制代码
// 1. Predicate 委托逆变
Predicate<Bird> f3 = a => true;
Predicate<Sparrow> f4 = f3; 
// 逆变:父类泛型 → 子类泛型

// 2. Action 委托逆变
Action<object> f5 = a => { };
Action<Bird> f6 = f5;  

// 3. Func 参数逆变
Func<Bird, int> f7 = a => 10;
Func<Sparrow, int> f8 = f7; 

4. 常见支持逆变的系统接口/委托

  • Action<in T>

  • Predicate<in T>

  • IComparer<in T>

  • IEqualityComparer<in T>


五、既协变又逆变:Func<in T, out TResult>

Func 委托同时拥有两种特性:

  • 参数 in T:支持逆变(父参数 → 子参数)

  • 返回值 out TResult:支持协变(子返回值 → 父返回值)

万能双向转换委托!


六、最终总结对照表(必背)

类型 关键字 转换方向 适用位置
协变 out 子类泛型 → 父类泛型 返回值、集合读取
逆变 in 父类泛型 → 子类泛型 方法输入参数

七、完整可运行基类代码

cs 复制代码
public class Bird
{

}
public class Sparrow : Bird
{

}

八、口诀(秒杀面试题)

out协变,子转父;

in逆变,父转子;

List普通不转换,接口委托才支持;

参数in来返回out,双向Func最省事。

相关推荐
软萌萌的13 分钟前
Python零基础从入门到精通详细教程-数据类型
开发语言·windows·python
东北赵四10 分钟前
关于Java泛型的知识点及其相关面试题
java·windows·python
SunnyDays10111 小时前
C# 加密和解密 PDF:设置密码、AES 加密及操作权限
pdf·c#·加密 pdf·解除 pdf 密码·pdf 权限设置·pdf 文档安全
飞翔的馒2 小时前
Claude Code 快速开始
microsoft
布鲁飞丝2 小时前
对 .NET线程 异常退出引发程序崩溃的反思
数据库·c#·.net
邱邱玩编程2 小时前
环境变量速切脚本【Windows】
windows
SamChan902 小时前
WebSocket实现PDF翻译进度实时推送:Go后端+React前端的完整方案
前端·websocket·pdf·c#·react·机器翻译
geovindu3 小时前
CSharp: LogHelper
开发语言·后端·c#·.net
console.log('npc')3 小时前
OpenClaw 使用教程:开源 AI Agent 编排框架完全指南
人工智能·microsoft·ai编程·openclaw
caishenzhibiao3 小时前
顺势捕猎者副图 同花顺期货通指标
java·c语言·c#