泛型接口的抗变(也叫逆变 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
当泛型参数用 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”为 逆变。
}