C#:由泛型產生的協變和逆變
阿新 • • 發佈:2020-11-29
協變
泛型委託間的協變
當泛型委託中型別引數作為委託方法的返回值時:
//該委託接收一個的方法是:無引數、返回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和CreateFactory是兩種不相容的資料型別;從下面程式碼可以看出:
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委託:
為了執行帶有零個形參且有返回值的方法,而宣告的Func委託:
為了執行帶有一個形參且有返回值的方法,而聲名的Func<T,TResult>委託:
以上便是對,因派生關係而產生的協變、逆變關係的知識點的總結,記錄下來以便以後查閱。