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

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








