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最省事。

相关推荐
h39743 小时前
DirectShow过滤器开发-视频源过滤器
c++·windows·音视频·directshow
曹牧5 小时前
C#:未被引用的函数
c#
java资料站9 小时前
二、Spring AI Alibaba · ChatModel
java·windows·spring
北冥you鱼10 小时前
Go 语言空接口(interface{})使用场景与最佳实践
开发语言·windows·golang
java资料站10 小时前
五、Spring AI Alibaba · Memory · Saver(短期会话)
java·spring·microsoft
上位机妹子11 小时前
EF Core 两种继承映射:每层次一张表(TPH)与每类型一张表(TPT)
数据库·c#·.netcore·ef core
夏虫不可与之言冰12 小时前
Windows WSL2 (Ubuntu) 搭建 ESP‑IDF 6.1 开发环境全过程演示
linux·windows·ubuntu·esp32·乐鑫科技·乐鑫eim·乐鑫代理商
小羊没烦恼!12 小时前
jQuery1.5的改进细节
java·服务器·开发语言·前端·c#
下页、再停留13 小时前
【C#桌面客户端系列学习-8】批量导出数据为csv格式
开发语言·c#·visual studio
波力海苔夹心脆67514 小时前
C# 视觉检测实战:PLC 按钮触发 VisionPro 检测,OK 亮灯 / NG 灭灯(西门子 PLC,含界面显示与图片存档)
开发语言·经验分享·c#·自动化·视觉检测·.net