Sunday, September 11, 2011

Covariance In .Net(ZT)

Problem:
我们可能会遇到这样的事情,就是在一些使用了泛型的接口或者数组中,即使只有泛型参数的类型不同,这些类型也不能像普通类一样互相转换

Eg.

class Animal {}
class Bear : Animal {}
class Camel : Animal {}
...
Stack<Bear> bears = new Stack<Bear>();
Stack<Animal> animals = bears;    // Compile error
Bear b=new Animal(); // no compile error
.....

什么是 Covariance

Covariance 用于数组、泛型接口和泛型委托中,如果存在泛型接口 I<T> 的两个实例 I<A> 和 I<B>,而 A 和 B 之间有继承关系(A is base, B is superclass),且 A > B。如果允许 I<B> 隐式转换为 I<A>,则成 I<T> 支持 Covariance。

C#数组的协变(Array Covariance) s supported from .net Framework 1.0

Eg.

object[] array=new string[10];// no compile error

but it is not type safe

array[0]=1; // will get run-time error


From .Net 4
如果存在类 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> 如下。

image

这就意味着 .NET Framework 4.0 中的 IEnumerable<T> 支持 Covariance。于是,下面的代码是可以工作的 。

image

我们也可以自己实现 Covariance。如下面的例子,类 A  和 类 B 具备继承关系,下面的转换都是合法的。因为 object > A > B,因此 B –> A,B –> object 以及 A –> object 均有效。

image
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>。

0 Comments:

Post a Comment

<< Home