協變和逆變,這兩個詞的翻譯實在很難表達出他們的真實含義。其實他們是繼承和多態的衍生物,而且在.Net 1.0 和2.0中都提供了某種程度的支持,只是在.Net 4.0中支持的更加完善了。
簡單說來,協變和逆變就是希望支持更多情況的隱式類型轉換,使得我們的編程更加方便,而通常來說只有具備繼承關系的兩個對象才可以發生隱式類型轉換,如Base b=new Derived(). 協變和逆變則使得更多的類型之間可以發生隱式類型轉換,如通過協變以下代碼可以正常工作:
Func<Derived> dFunc=GetDFunc(); Func<Base> bFunc = dFunc; IEnumerable<Derived> dEnum=GetDEnum(); IEnumerable<Base> bEnum = dEnum;
那么我們為什么需要這種功能呢?讓我們從一個多態的例子開始:
abstract class Animal { internal abstract void Eat(); } abstract class Mammal : Animal { } class Tiger : Mammal { internal override void Eat() { Console.WriteLine("Tiger eat"); } }
class Program { static void Main(string[] args) { Tiger tiger = new Tiger(); FeedAnimal(tiger); } static void FeedAnimal(Animal animal) { animal.Eat(); } }
雖然FeedAnimal方法接受的參數類型為Animal ,但是當我們傳入一個Tiger 的實例,方法能夠編譯通過,并得到正確的執行。這是一個典型的多態運用。之所以能夠這么做是因為Tiger 繼承于Animal ,在FeedAnimal方法中對Animal對象的各種操作,Tiger 對象同樣支持,而不會發生調用了animal的某個方法A,而tiger中不存在A方法的情況。 那么如果我們把這一原則推而廣之,就出現了協變這一概念。
static void FeedAnimal(Func<Animal> animalCreator) { var animal=animalCreator(); animal.Eat(); } static Tiger CreateTiger() { return new Tiger(); }
現在我們讓FeedAnimal方法接受一個能夠返回Animla的delegate,而同時又創建了一個能夠返回Tiger的方法。按照之前的推理,FeedAnimal方法無非就是對delegate中返回的Animal對象進行各種操作,那么如果給它一個返回Tiger對象的delegate,也應該能夠正常的工作。所以下面這段代碼是可以在.NET 2.0中工作的。
class Program { static void Main(string[] args) { FeedAnimal(CreateTiger); } }
這就是所謂的協變,我們希望兩個對象之間,除了繼承關系外也能做隱式類型的轉換,因為我們認為這種轉換是合理的,是類型安全的。
大家可能很奇怪,如果.Net 2.0就已經支持了以上的代碼,那么4.0又搞了些什么?
先來看看下面的代碼:
class Program { static void Main(string[] args) { Tiger tiger = new Tiger(); FeedAnimal(tiger); } static void FeedAnimal(Animal animal) { Console.WriteLine("Feed Animal");
}
static void FeedAnimal(Mammal mammal)
{Console.WriteLine("Feed Mamal");
} }
以上這段代碼的輸出結果是 Feed Mammal,當碰到兩個匹配的重載方法時,.Net編譯器選擇了形參類型在繼承樹上更接近自己的方法。可是如果我們把Animal改成Func<Animal> 以下代碼就編譯出錯了,編輯器不知道選擇哪個方法作為匹配。
class Program { static void Main(string[] args) { FeedAnimal(CreateTiger); }
static Tiger CreateTiger() { return new Tiger(); }
static void FeedAnimal(Func<Animal> animalCreator) { var animal=animalCreator(); animal.Eat(); } static void FeedAnimal(Func<Mammal> animalCreator) { var animal = animalCreator(); animal.Eat(); }
究其根本,在C# 2.0中你無法實現以下delegate間的隱式類型轉換
Func<Tiger> tFunc = CreateTiger; Func<Animal> aFunc = tFunc;
而C# 4.0, 使得上述代碼能夠成功運行,之前的兩個方法他也能夠做出正確的選擇。這是通過out關鍵字實現的。 注意Func<TResult> 這個delegate在C# 2.0和4.0中的定義是不同的:
//C# 2.0 public delegate TResult Func<TResult>(); //C# 4.0 public delegate TResult Func<out TResult>();
out關鍵字表示該類型用于返回值,而返回值可以發生協變,因為一個方法如果能夠接受一個返回Animal的delegate,那么他必然也可以接受一個返回Tiger的delegate,因為他對animla的操作同樣可以作用于tiger上。
既然delegate能夠支持協變,那么interface也應該給予支持,因為它無非就是多個delegate罷了。
以下代碼在C# 2.0中式不能通過編譯的, 這樣看來interface還不如delegate支持的好。
static void Main(string[] args) { List<Tiger> tigers = new List<Tiger>(); FeedAnimals(tigers); } static void FeedAnimals(IEnumerable<Animal> animals) { foreach (var animal in animals) animal.Eat(); }
下面的代碼也就更加不行了:
List<Tiger> tigers = new List<Tiger>();
IEnumerable<Animal> animlas = tigers;
到了C# 4.0中,由于out關鍵字的緣故,以上兩段代碼都能正常工作了。而IEnumerable<T>的定義也被改為
public interface IEnumerable<out T> : IEnumerable { IEnumerator<T> GetEnumerator(); }
之前舉得例子都是描述協變的,其實逆變無非就是擴大了輸入參數的類型轉換的可能,不過方向是反的。
static void FeedAnimal(Animal animal) { animal.Eat(); } static void Execute(Action<Mammal> tAct) { tAct(new Tiger()); }
在Execute方法中, 我們接受一個類型為Action<Mammal> 的delegate,我們在使用這個delegate時,可以傳入各種類型的Mammal,比如Tiger, Lion。如果我們傳入的delegate能夠作用于Animal對象,那么他自然可以作用于Tiger, Lion。 所以我們可以傳入一個輸入類型更抽象的delegate。因此,在逆變的支持下我們可以寫出以下代碼。
static void Main(string[] args) { Action<Animal> aAct = FeedAnimal; Action<Mammal> mAct = aAct; Execute(aAct); }
參考資料:
eric's series on covariance and contravariance
浙公網安備 33010602011771號