转发:https://www.cnblogs.com/Ninputer/archive/2008/11/22/generic_covariant.html
背景知识:协变和逆变
假设有这样两个类型:TSub是TParent的子类,显然TSub型引用是可以安全转换为TParent型引用的。如果一个泛型接口IFoo
.NET 4.0引入的泛型协变、逆变性
刚才我们讲解概念的时候已经用了泛型接口的协变和逆变,但在.NET 4.0之前,C#不支持泛型的这种可变性。不过它们都支持委托参数类型的协变和逆变。由于委托参数类型的可变性理解起来抽象度较高,所以我们这里不准备讨论。已经完全能够理解这些概念的读者自己想必能够自己去理解委托参数类型的可变性。在.NET 4.0之前为什么不允许IFoo
1 interface IFoo2 { 3 void Method1(T param); 4 T Method2(); 5 }
如果我们允许协变,从IFoo
1 interface ICo<out T> 2 { 3 T Method(); 4 } 5 6 interface IContra<in T> 7 { 8 void Method(T param); 9 }
在.NET Framework中,许多接口都仅仅将类型参数用于参数或返回值。为了使用方便,在.NET Framework 4.0里这些接口将重新声明为允许协变或逆变的版本。例如IComparable
下面提起几个泛型协变和逆变容易忽略的注意事项:
1)仅有泛型接口和泛型委托支持对类型参数的可变性,泛型类或泛型方法是不支持的。
2)值类型不参与协变或逆变,IFoo
3)声明属性时要注意,可读写的属性会将类型同时用于参数和返回值。因此只有只读属性才允许使用out类型参数,只写属性能够使用in参数。
协变和逆变的相互作用
这是一个相当有趣的话题,我们先来看一个例子:
1 interface IFoo<in T> 2 { 3 } 4 5 interface IBar<in T> 6 { 7 void Test(IFoofoo); //对吗? 8 }
1 interface IFoo<in T> 2 { 3 } 4 5 interface IBar<out T> 6 { 7 void Test(IFoofoo); 8 }
你能看出上述代码有什么问题吗?我声明了in T,然后将他用于方法的参数了,一切正常。但出乎你意料的是,这段代码是无法编译通过的!反而是这样的代码通过了编译:
什么?明明是out参数,我们却要将其用于方法的参数才合法?初看起来的确会有一些惊奇。我们需要费一些周折来理解这个问题。现在我们考虑IBar
刚才提到了方法参数上协变和逆变的相互影响。那么方法的返回值会不会有同样的问题呢?我们看如下代码:
1 interface IFooCo<out T> 2 { 3 } 4 5 interface IFooContra<in T> 6 { 7 } 8 9 interface IBar<out T1, in T2> 10 { 11 IFooCoTest1(); 12 IFooContra Test2(); 13 }
我们看到和刚刚正好相反,如果一个接口需要对T进行协变或逆变,那么这个接口所有方法的返回值类型必须支持对T同样方向的协变或逆变。这就是方法返回值的协变-逆变一致原则。也就是说,即使in参数也可以用于方法的返回值类型,只要借助一个可以逆变的类型作为桥梁即可。