协变(out)和逆变(in)是C#编译期强制校验的类型安全机制:out用于只输出(返回值)的泛型参数,支持子类→父类转换;in用于只输入(参数)的泛型参数,支持父类→子类转换;二者不可共存,且仅适用于接口和委托。

协变和逆变是 C# 中让泛型接口和委托支持“安全类型转换”的机制,不是语法糖,也不是运行时魔法——它们由编译器在编译期强制校验,核心目标只有一个:在保持类型安全的前提下,让继承关系能自然地“传导”到泛型参数上。
为什么 IEnumerable 能直接赋值给 IEnumerable?
因为 IEnumerable 声明了 T 是协变的(用 out 修饰),意味着:只要 string 是 object 的子类,那 IEnumerable 就可隐式转为 IEnumerable。这符合直觉——你从集合里“读出来”的东西,子类能当父类用(里氏替换原则)。
- ✅ 合法:
IEnumerable
strings = new List (); IEnumerable - ❌ 非法:
IList
(因为strings = new List (); IList IList既有Get又有Set,无法同时满足协变/逆变约束) - ⚠️ 注意:数组也支持协变(如
string[]→object[]),但它是**不安全的**——运行时可能抛ArrayTypeMismatchException,而泛型协变是编译期就拦住的,更可靠。
为什么 Action 能接收 Action?
因为 Action 声明了 T 是逆变的(用 in 修饰),意思是:你传进去的委托,参数类型越“宽”(越靠继承链顶端),它越能接受“窄”的实际参数。比如一个能处理任意 object 的方法,当然也能安全处理 string。
Shell本身是一个用C语言编写的程序,它是用户使用Linux的桥梁。Shell既是一种命令语言,又是一种程序设计语言。作为命令语言,它交互式地解释和执行用户输入的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。它虽然不是Linux系统核心的一部分,但它调用了系统核心的大部分功能来执行程序、建立文件并以并行的方式协调各个程序的运行。因此,对于用户来说,shell是最重要的实用程序,深入了解和熟练掌握shell的特性极其使用方法,是用好Linux系统
- ✅ 合法:
Action
- ❌ 非法:
Func
(等等——这里错了?不,funcStr = () => "a"; Func Func是协变的,所以Func✅ 可赋给Func;真正非法的是反过来) - ? 关键记忆点:
out= 输出(只读、返回值)、in= 输入(只写、参数);违反这个方向就会编译失败。
自己定义接口时,in 和 out 怎么选?
不是看“类的继承方向”,而是看泛型参数 T 在接口方法中“出现的位置”:
- 如果
T只出现在返回值位置(如T Get();),用out T(协变); - 如果
T只出现在方法参数位置(如void Set(T value);),用in T(逆变); - 如果
T同时出现在返回值和参数中(如T Convert(T input);),那就不能加 in/out——只能是不变(invariant),否则类型系统无法保证安全。 - ⚠️ 常见坑:
IComparer是in T,因为Compare(T x, T y)两个参数都是输入;IEqualityComparer同理;而IComparable是in T(CompareTo(T other)参数是输入),别记反。
最易被忽略的一点:协变/逆变只适用于泛型接口和委托,不支持泛型类(如 List)、不支持值类型(int、DateTime 等不能参与 in/out)、也不支持泛型约束中的协变类型(比如 where T : out U 是非法的)。它是一套编译器强约束的“契约”,不是开发者自由发挥的空间。








