上一篇文章我们介绍了基于数组的 Covariance,数组的 Covariance 是一个隐含的类型转换,但很可惜这个隐含转换并不适用于接口以及委托。如果在接口和委托中存在类型参数,而希望从一个泛型接口转换成另外一个泛型接口,或者委托之间的转换的话,必须要引入泛型接口和委托的 Covariance。
我们说过了,Covariance 允许这样的类型转换:如果存在类 A 和 B,A 是 B 的基类(A > B),那么 I<T> 是一个泛型接口,T 约束为 A(where T : A),那么从类型 I<B> 可以转换为类型 I<A>。
对于委托,如果存在类 A 和 B,A 是 B 的基类(A > B),那么 D<T>() 是一个泛型委托,T 约束为 A(where T : A),那么从委托 D<B> 可以转换为委托 D<A>。
如何声明一个接口或者委托支持 Covariance
C# 4.0 现在提供一个新的约束性语法,在声明返型接口或委托时,可以在泛型参数前面加上 in 或者 out 来表示该接口或委托支持 Contra-variance 或 Covariance。这篇文章只涉及到 Covariance,因此我们需要一个 out 约束。
某一些 CLR 系统内置的类,如 IEnumerable<T>,已经被声明成了 IEnumerable<out T>。我们通过 Metadata 看到的 System.Collections.Generic.IEnumerable<T> 如下。
这就意味着 .NET Framework 4.0 中的 IEnumerable<T> 支持 Covariance。于是,下面的代码是可以工作的 。
我们也可以自己实现 Covariance。如下面的例子,类 A 和 类 B 具备继承关系,下面的转换都是合法的。因为 object > A > B,因此 B –> A,B –> object 以及 A –> object 均有效。
Covariance 的约束条件
按照惯例,不是所有具备泛型参数的接口和委托都可以用来支持 Covariance。如果类型 I<T> 或委托 D<T> 的类型参数 T 具备 out 约束,那么:
- T 是类、结构或者枚举类型。
- T 是非泛型接口或者委托类型。
- T 是元素类型支持 Variance 的数组。
- T 是一个没有被定义为 out 约束的类型参数类型。
另外,支持 Covariance (有 out 约束)的泛型接口或委托存在以下限制:
- 他们不能包含一个具备类型参数表的事件(但自定义事件或者使用委托声明的事件除外)。
- 他们不能包含内部类(Nested Class)、结构或枚举类型。
其他需要注意的地方
作为最佳实践,泛型接口的类型参数 T 一般为 out 约束(Covariance),泛型委托的类型参数如果是作为参数列表的,则一般为 in 约束(Contra-variance),而类型参数作为返回值的,一般为 out 约束(Covariance)。关于这一点请参照 IEnumerable<out T> 和 Func<in T, out TResult>。