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

相关推荐
天下无敌笨笨熊6 小时前
C#视频开发心得
开发语言·c#·音视频
LccKyI6 小时前
C#基础编程 面试题汇总(三)
笔记·c#
百年੭ ᐕ)੭*⁾⁾6 小时前
Redash部署指南(Windows docker部署)
大数据·windows·docker·数据可视化·redash
半亩码田8 小时前
C#转Python第4.1篇:当 try-catch 遇上 try-except:异常处理的大不同
开发语言·python·c#
cd_949217218 小时前
具身大脑成机器人产业核心底座,星源智技术落地与产业布局解析
人工智能·microsoft·机器人
z落落9 小时前
C# Modbus-RTU 串口读写设备+CRC16+邮件告警
开发语言·c#
DS随心转小程序9 小时前
Gemini只能导出了一部分Word文档,很少的一部分,不是我这个窗口所有的对话内容?
开发语言·人工智能·c#·word·豆包·deepseek·ai导出鸭
星云API|微信接口开发9 小时前
企业微信二次开发:企业微信外部群机器人怎么做?从群消息接收到事件处理
microsoft·机器人·企业微信
格林威9 小时前
C# 相机图像配合频闪光源:实现高速稳定拍摄的几个方法
开发语言·网络·人工智能·数码相机·计算机视觉·c#·视觉检测
CSDN_RTKLIB9 小时前
CAD绘图单位
c#