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
.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/.