C# 泛型中的协变和逆变

C#中使用了继承和泛型这两种机制来编写跨类型的可复用的代码。泛型是包含了可变的类型参数的类型,在运行时用具体的类型填充泛型的类型参数,以创建一个具体的类型。在技术上,称未填充类型参数的类型为开放类型,已填充了类型参数的类型为封闭类型

//public class List : IList, System.Collections.IList, IReadOnlyList
//List是开放类型,List是封闭类型
List d = new List();

一. 与泛型相关的类型转换

泛型的封闭类型和其他类型一样,也可以进行类型转换,其类型转换可以分为两种情形:

1. 所填充的类型参数不变。这种情况可以把封闭类型当作普通的类型,适用普通的类型转换规则。

class Base {}
class Derived : Base {}

//List可以隐式转换为IEnumerable
//因为List间接实现了IEnumerable接口
IEnumerable d = new List();

2. 所填充的类型参数发生改变,这种情况下,称为泛型的协变或逆变,或统称为泛型的可变性(Variance)。具体地:

协变(Covariance):接收类型所填充的类型参数比原本填充的类型参数派生度更低。

逆变(contravariance):接收类型所填充的类型参数比原本填充的类型参数派生度更高。

//IEnumerable可以隐式转换为IEnumerable
//因为IEnumerable的类型参数是可协变的
//这种转换称为协变
IEnumerable d = new List();
IEnumerable b = d;

//Action可以隐式转换为Action
//因为Action的类型参数是可逆变的
//这种转换称为逆变
Action b = (target) => { Console.WriteLine(target.GetType().Name); };
Action d = b;
d(new Derived());

上面的代码在编译和运行时都是类型安全的。

二. 协变类型参数和逆变类型参数

泛型中的类型参数默认是不可变的,即既不可以协变,也不可以逆变,如果想要实现可变,则需要使用相应的关键字修饰类型参数。使用关键字 in 指定类型参数为可逆变的,使用关键字 out 指定类型参数为可协变的。

//这是一个.NET内置的泛型委托
//类型参数T是可逆变的
//类型参数TResult是可协变的
public delegate TResult Func(T arg);

一般来说,协变类型参数可以用作委托的返回类型,逆变类型参数可以用作参数类型。对于接口,协变类型参数可以作为接口方法的返回类型,逆变类型参数可以作为接口方法的参数类型。否则的话,可能会发生运行时错误,例如下面的代码:

//编译时错误:
//变型无效: 类型参数“TResult”必须是在“MyFunc.Invoke()”上有效的 协变式。
//“TResult”为 逆变。
public delegate TResult MyFunc();

//试想一下,如果上面的声明是有效的
//下面的第一行语句就可以正常执行
//而第二行语句则会出现运行时错误
//因为Base类型不能隐式转换为Derived类型
//因此编译器不允许上面的声明通过
MyFunc foo = ()=>new Base();
foo();

可变类型参数的使用还具有以下限制

  • 仅可在泛型接口和泛型委托的声明中使用
  • 仅适用于引用类型,如果使用值类型填充类型参数,则得到的封闭类型是不可变的
  • 可变性不能用于委托链,即不能组合两个封闭类型不完全一致的委托
//编译时错误
//无法将类型“System.Func”隐式转换为“System.Func”
Func foo = new Func(()=>3);


Func bar = ()=>new Derived();
Func foo = bar;
Console.WriteLine(foo().ToString()); //Derived

foo = ()=>new Base();
foo += bar;
Console.WriteLine(foo().ToString());
//System.ArgumentException: Delegates must be of the same type

三. 内置的可变类型

.NET类库中有很多泛型接口或委托都使用了可变的类型参数,下面列举一些这类接口和委托

1. 具有协变类型参数的泛型接口

public interface IEnumerable : IEnumerable
{
    // Returns an IEnumerator for this enumerable Object.  The enumerator provides
    // a simple way to access all the contents of a collection.
    /// 
    new IEnumerator GetEnumerator();
}

2. 具有逆变类型参数的泛型接口

public interface IComparer
{
    // Compares two objects. An implementation of this method must return a
    // value less than zero if x is less than y, zero if x is equal to y, or a
    // value greater than zero if x is greater than y.
    // 
    int Compare(T x, T y);
}

3. 具有协变类型参数的委托类型

public delegate TResult Func();

4. 具有逆变类型参数的委托类型

public delegate void Action(T obj); 

5. 同时具有协变类型参数和逆变类型参数的委托类型

public delegate TResult Func(T arg);

参考资料

1. Covariance and contravariance in generics,https://docs.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance;

2. Covariance and Contravariance (C#),https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/covariance-contravariance/;

3. Microsoft Reference Source,https://referencesource.microsoft.com/.

你可能感兴趣的:(c#,泛型)