This page looks best with JavaScript enabled

C#泛型接口的抗变和协变

 ·  ☕ 2 min read

泛型接口的抗变(也叫逆变 Contravariance)和协变(Covariance)有什么区别?

先说定义, 协变和逆变能够实现数组类型、委托类型和泛型类型参数的隐式引用转换。

在面向对象设计中子类的实例可以赋值给父类类型的变量, 这是协变. 如 object o = “hello” 是正确的, 这个是符合里氏替换原则, 但是 string s = new object(), 这样的隐式转换语法不允许的, 除非自定义了等号操作符. 但是逆变让这种隐私转换变为合法的,当然有很多前置条件,
首先必须是数组类型,泛型委托或者泛型接口.
泛型参数必须用 in 关键字修饰. 也就是说这个泛型参数只能作为方法的形参类型, 而不能是返回值.
.net framework4 之后支持变体泛型接口.
委托的逆变的好处可以使用一个事件处理程序, 而不是多个单独的处理程序, 下面代码演示了委托的逆变.
泛型接口的逆变的设计还有待发掘, 欢迎补充演示代码.

下面的代码演示了分配兼容性, 协变和逆变的差异
分配兼容性
string str = “test”;
子类实例可以赋值给父类
object obj = str;

协变接口, 用out关键字参数定义

 public interface IEnumerable<out T> : System.Collections.IEnumerable
 IEnumerable<string> strings = new List<string>();  
 IEnumable<object> 的泛型参数类型是 object, IEnumrable<string> 泛型参数类型是string,  下面这样的赋值 协变符合分配兼容性
 IEnumerable<object> objects = strings;  

逆变委托

假设有一个这样的类函数

 static void SetObject(object o) { }   
 public delegate void Action<in T>  //逆变的T, 支持父类实例赋值给子类类型
 Action<object> actObject = SetObject;  给具有逆变的委托赋值一个方法实例

下面的操作是逆变, 父类参数object实例赋值给子类类型string
逆变违反了分配兼容
Action actString = actObject;

当泛型参数用 out 关键字修饰, 意味着其为协变泛型接口, 这时接口的方法只能把 T 作为函数的返回值类型, 而不能作为函数的形参类型 如:
协变接口

 public interface IEnumerable<out T> : System.Collections.IEnumerable
 {
     T MyFunciton();  // 正确, T是协变
     //编译错误 error CS1961: 变型无效: 类型参数“T”必须是在
   “  interface1<T>.function2(T)”上有效的 逆变式。“T”为 协变。
      void MyFunciton2(T t); 
 }

当泛型参数用 in 关键字修饰, 意味着逆变 , T只能作为方法的形参类型, 不能作为函数的返回值类型

 public interface IEqualityComparer<in T> 
 {
       void MyFunction(T t); //正确,   
       T MyFunction2();  
       //编译错误  error CS1961: 变型无效: 类型参数“T”必须是在“interface2<T>.function2()”上有效的 协变式。“T”为 逆变。
 }

泛型接口的抗变和协变统称为变体泛型接口 以上资料参考:

c#中的逆变和协变
委托中变体
泛型接口中的变体
在泛型集合的接口中使用变体

Share on

James
WRITTEN BY
James
Unity Developer