什么是协变?什么是逆变?为什么要引入
我们知道,面向对象编程告诉我们,可以把子类安全地赋给父类,如
object obj=new object(); string str=new string(); obj=str;
但是我们能将List<string>赋给List<object>吗,答案是不能。虽然string继承了object,但是List<string> 没有继承List<objetct>,他们是完全不同的类。
有没有什么办法可以实现我们想要的”继承"呢。答案是,对类无法实现,但是对接口和委托可以实现。为什么只对类和接口实现呢?因为接口和委托存在的目的是调用方法。这就要用到协变和逆变。
1 void Main() 2 { 3 IMyList<object> obj=null; 4 IMyList<string> str=new MyList<string>(); 5 obj=str; 6 obj.print(); 7 } 8 9 interface IMyList<T> 10 { 11 void print(); 12 } 13 class MyList<T>:IMyList<T> 14 { 15 public void print() 16 { 17 Console.WriteLine(this.ToString()); 18 } 19 }
以上代码能编译通过吗?不能,错误发生在第5行,将一个IMyList<string>变量str赋值给一个IMyList<object>变量obj,显然是行不通的。怎么办?
这就要用到协变,将第9行代码改为如下即可
interface IMyList<out T>
”out“什么意思?out表示这个参数只能作为输出不能作为输入,即不知作为输入参数类型而不能作为返回类型,如果编写如下代码
interface IMyList<out T> { void print(T t); }
将不能通过。那为什么上面添加”out"就可以了。这是因为我们使用接口的目的是要调用具体类的方法(因为接口内不能有字段,只能有属性和方法),像上面程序的第6行,调用obj.print(),其本质还是调用str.print(),这就涉及把obj的参数 传递给str的参数,我们知道,obj的参数是object,而str的参数是string,而object不能安全地赋值string的,反之却可以。这就需要我们在泛型接口中告诉编译器,这个参数是只能出(out)不能进(in)的,所以要加"out”参数就可以保证类型安全。
那我把第5行改为
str=obj
会怎样?这就要用到逆变了,将第9行改为
interface IMyList<in T>
即可,原理同上。只不过如果调用str.print(),其本质上要调用的是obj.str(),传入参数是需要将string转换为object,这个没有问题,但是返回值却要将object 转换为string,这个却不行,所以接口中的这个T是只能进(in)不能出(out)的。
委托的协变和逆变和接口类似。
总之,如果在接口或委托中的泛型参数T之前不加"out"和“in”关键字,出入安全考虑,如果T不同,像上面的IMyList<object>和IMyList<string>,系统会认为他们之间完全不同,不能相互转化。如果想要支持相互转化,那就要根据情况添加"in"和“out”关键字。
至于为什么接口和委托实现了协变和逆变,我想可能是因为接口和委托都是为了调用方法而存在的吧,当然接口还有其他用途。