C#中泛型的协变和逆变

今天读CLR Via C#时看到了泛型的协变和逆变.泛型倒是熟悉.协变和逆变还真没怎么注意过.回来之后翻了下资料才慢慢明白了.做下记录.

可变性的类型:协变性和逆变性
可变性是以一种类型安全的方式,将一个对象当做另一个对象来使用.如果不能将一个类型替换为另一个类型,那么这个类型就称之为:不变量.协变和逆变是两个相互对立的概念.

先看看MSDN中的协变和逆变的概念

协变和逆变都是术语,前者指能够使用比原始指定的派生类型的派生程度更小(不太具体的)的类型,后者指能够使用比原始指定的派生类型的派生程度更大(更具体的)的类型。

也就是说.

如果某个返回的类型可以由其派生类型替换,那么这个类型就是支持协变的.如string->object
如果某个参数类型可以由其基类替换,那么这个类型就是支持逆变的.如object->string

下面看下示例:

.net中IEnumerable的泛型类型T是支持协变的.定义如下

类型参数 T 要枚举的对象的类型。此类型参数是协变。即可以使用指定的类型或派生程度更高的类型。

那么这样的代码是可以通过编译且可以正确运行的:

IEnumerable<string> stringList = new List<string>() { "" };
IEnumerable<object> objectList = stringList;  

在上面的实例中,在C# 4.0之前是不能正常编译的,除了对赋值给基类集合时将子类集合做一个强制转换,但是在运行时仍然会抛出一个类型转换的异常.
这个是协变.我们看看逆变的例子
.net中Action<T>的泛型类型T是支持逆变的.定义如下

此委托封装的方法的参数类型。此类型参数是逆变。即可以使用指定的类型或派生程度更低的类型。

示例代码:

Action<object> objectAction = o => Console.WriteLine(o.GetType().ToString());
Action<string> stringAction = objectAction;  

在c#中.使用inout关键字来标记逆变和协变
所以如果有一个泛型参数标记为out,则代表它是用来输出的,只能作为结果返回,而如果有一个泛型参数标记为in,则代表它是用来输入的,也就是它只能作为参数.
下面我们再看看代码.
这两个类并没有任何意义.只是为了说明继承关系.

public class People
{

}
public class Teacher : People
{

}  

下面看看逆变.

public interface ITest<in T>
{
    void ShowType(T t);
}
public class Test<T> : ITest<T>
{
    public void ShowType(T t)
    {
        Console.WriteLine(t.GetType().ToString());
    }
}  

那么这样的代码是可以通过编译的.

ITest<People> peopleTest = new Test<People>();
ITest<Teacher> teacherTest = peopleTest;  

那么协变呢?

public interface ITest<out T>
{
    T GetEntity();
}
public class Test<T> : ITest<T>
{
    public T GetEntity()
    {
        return default(T);
    }
}

因为out关键字.所以这样的代码是可以通过编译的.

ITest<Teacher> teacherTest = new Test<Teacher>();
ITest<People> peopleTest = teacherTest;  

发表评论

电子邮件地址不会被公开。 必填项已用*标注