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

相关推荐
鱼宵1 小时前
取集合第一个元素,Java 为什么要写三种写法?
java·windows·python·java21
caimouse1 小时前
MonoDevelop 编译用户应用程序时调用的是哪个编译器
c#
kingx32 小时前
Windows Server 2008 应急加固实战:7 项入侵痕迹排查与修复
数据库·windows·提权·应急响应·加固
will_net3 小时前
apollo 站点部署&C#接入
开发语言·c#·apollo
老牛日常随笔4 小时前
微软开源Flint Chart:AI画图表总是画不对?让编译器来画,AI只写数据就行
人工智能·microsoft
海域云SeaArea_5 小时前
Windows Server 2008 R2 从零安装 IIS7.5(二):php.ini 配置
开发语言·windows·php
IsITGirl9 小时前
VMware Fusion(Mac)Windows ARM 虚拟机 NAT (vmnet8) 报错解决记录
windows·macos·.net·桥接模式
Rudon滨海渔村9 小时前
台式机Win10实现“音量+”和“音量-”,键盘映射方案
windows·计算机外设·powertoys
2501_915921439 小时前
KXBuild CI 实操,Windows/Linux 服务器接入 Jenkins 打包 iOS IPA
服务器·windows·vscode·ios·ci/cd·个人开发·敏捷流程