C#:泛型的协变和逆变
协变
泛型委托间的协变
当泛型委托中类型参数作为委托方法的返回值时:
//该委托接收一个的方法是:无参数、返回T类型的
delegate T CreateFactory<T>();
class Program
{
static void Main(string[] args)
{
CreateFactory<Dog> createDog = MakDog;
//Cannot convert source type 'GenericDemo.CreateFactory<GenericDemo.Dog>' to target type 'GenericDemo.CreateFactory<GenericDemo.Animal>'
CreateFactory<Animal> createAnimal = createDog;
Console.ReadLine();
}
static Dog MakDog()
{
return new Dog();
}
}
class Animal
{
public int Legs { get; set; } = 4;
}
class Dog : Animal
{
}
上面代码会产生编译错误,是因为CreateFactory< Dog>和CreateFactory< Animal>是两种不兼容的数据类型;从下面代码可以看出:
static void Main(string[] args)
{
CreateFactory<Dog> createDog = MakDog;
//若is表达式结果为True,那么表明createDog兼容CreateFactory<Animal>类型
Console.WriteLine(createDog is CreateFactory<Animal>);
Console.ReadLine();
}
/*输出:False*/
在类型参数前加上out关键字修饰 就可以使赋值操作正常进行
delegate T CreateFactory<out T>();
out关键字修饰的类型参数,告诉编译器我们期望上面的赋值能够成功;而由out修饰了的泛型委托的构造类型之间便存在了一种协变关系。
泛型接口间的协变
当泛型接口的类型参数作为函数成员的返回值时:
interface IResultAble<T>
{
T GetModel();
}
class MyClass<T> : IResultAble<T>
{
public T[] Items { get; set; }
public T GetModel()
{
return Items.First();
}
}
class Program
{
static void Main(string[] args)
{
IResultAble<Dog> dog = new MyClass<Dog>();
IResultAble<Animal> animal = dog;
Console.ReadLine();
}
}
class Animal
{
public int Legs { get; set; } = 4;
}
class Dog : Animal
{
}
使用out关键字修饰泛型接口的类型参数,使赋值操作正常进行
interface IResultAble<out T>
{
T GetModel();
}
使用了out关键字,函数成员执行后总能返回一个期望的基类型引用。而赋值操作能够成功进行,靠的是out关键字;使用out关键字的泛型接口的构造类型间存在协变关系。
逆变
泛型委托间的逆变
当泛型委托的类型参数作为方法的形参时:
public delegate void HandlerSomething<T>(T param);
class Program
{
static void Main(string[] args)
{
HandlerSomething<Animal> showAnimalLegs = new HandlerSomething<Animal>(ShowLegs);
//Cannot convert source type 'GenericDemo.HandlerSomething<GenericDemo.Animal>' to target type 'GenericDemo.HandlerSomething<GenericDemo.Dog>'
HandlerSomething<Dog> showDogLegs = showAnimalLegs;
Console.ReadLine();
}
static void ShowLegs(Animal animal)
{
Console.WriteLine(animal.Legs);
}
}
class Animal
{
public int Legs { get; set; } = 4;
}
class Dog : Animal
{
}
使用in关键字修饰类型参数,可以使上面的赋值正常进行
public delegate void HandlerSomething<in T>(T param);
通过in关键字"貌似实现了基类型转向子类型的逆向转换",所以我们称in关键字所在的泛型委托的构造类型之间存在逆变关系。
泛型接口间的逆变
当泛型接口的类型参数作为函数成员的形参时:
interface IHandlerAble<T>
{
void PrinName(T value);
}
class MyClass<T> : IHandlerAble<T>
{
public void PrinName(T value)
{
Console.WriteLine(value.GetType().FullName);
}
}
class Program
{
static void Main(string[] args)
{
IHandlerAble<Animal> animal = new MyClass<Animal>();
//Cannot convert source type 'GenericDemo.IHandlerAble<GenericDemo.Animal>' to target type 'GenericDemo.IHandlerAble<GenericDemo.Dog>'
IHandlerAble<Dog> dog = animal;
Console.ReadLine();
}
}
class Animal
{
public int Legs { get; set; } = 4;
}
class Dog : Animal
{
}
使用in关键字修饰类型参数,可使上面的赋值操作正常进行
interface IHandlerAble<in T>
{
void PrinName(T value);
}
从泛型接口的赋值操作来看,似乎这是一种从父类型到子类型的一种逆向转换:所以in关键字修饰的泛型接口的构造类型间存在逆变关系。
协变、逆变也可隐式进行
当赋值号右边还未产生委托对象时,编译器可智能推断出委托类型间的协变、逆变关系:
delegate void MyAction<T>(T param);
delegate T MyFunc<T>();
class Program
{
static void Main(string[] args)
{
MyFunc<Animal> animal = CreateDog;
var result= animal.Invoke();
Console.WriteLine(result.GetType().FullName);
MyAction<Dog> getLegs = ShowLegs;
getLegs.Invoke(new Dog());
Console.ReadLine();
}
static Dog CreateDog()
{
return new Dog();
}
static void ShowLegs(Animal animal)
{
Console.WriteLine(animal.Legs);
}
}
/*
输出:
GenericDemo.Dog
4
*/
当赋值号右边产生了泛型委托对象时,就必须使用out、in关键字了
delegate void MyAction<in T>(T param);
delegate T MyFunc<out T>();
class Program
{
static void Main(string[] args)
{
MyFunc<Animal> animal = CreateDog;
var result = animal.Invoke();
Console.WriteLine(result.GetType().FullName);
MyFunc<Animal> animal1 = new MyFunc<Dog>(CreateDog);
animal1.Invoke();
MyAction<Dog> getLegs = ShowLegs;
getLegs.Invoke(new Dog());
MyAction<Dog> getLegs1 = new MyAction<Animal>(ShowLegs);
getLegs1.Invoke(new Dog());
Console.ReadLine();
}
static Dog CreateDog()
{
return new Dog();
}
static void ShowLegs(Animal animal)
{
Console.WriteLine(animal.Legs);
}
}
学习了逆变、协变后,我们就能明白.NET API 提供的泛型委托、泛型接口为什么都带着in、out关键字了
为了执行带有一个形参且无返回值的方法,而声明的Action< T>委托:
为了执行带有零个形参且有返回值的方法,而声明的Func< TResult>委托:
为了执行带有一个形参且有返回值的方法,而声名的Func<T,TResult>委托:
以上便是对协变、逆变的知识点的总结,记录下来以便以后查阅。