關於協變逆變,SolidMango的解釋是比較可取的。有了協變,比如,在需要返回IEnumerable<object>類型的時候,可以使用IEnmerable<string>來替代;有了逆變,比如,在需要接收IComparable<string>類型形參方法中,可以使用IComparable<object>類型實參來替代。
協變
先來體會協變。有2個具有繼承關系的父類和子類。
public class Animal{public string Name { get; set; }}public class Dog : Animal{public Dog(string dogName){Name = dogName;}}
現在有一個幫助類的方法的形參類型是父類集合IEnumerable<Animal>。
public class MyHelper{public void PrintAnimalNames(IEnumerable<Animal> animals){foreach (var animal in animals){Console.WriteLine(animal.Name);}}}
有了協變,可以在PrintAnimalNames方法中傳入IEnumerable<Dog>類型的實參替代IEnumerable<Animal>類型。
static void Main(string[] args){List<Dog> dogs = new List<Dog>(){new Dog("小狗petty"),new Dog("小狗lily")};//協變IEnumerable<Animal> animals = dogs;MyHelper myHelper = new MyHelper();myHelper.PrintAnimalNames(animals);Console.ReadKey();}
可見,在方法中基於基類接口類型的形參,調用該方法的時候可以傳入派生類接口類型的實參。
逆變
再來體會逆變。依然是2個具有繼承關系的父類和子類。
public class Animal{public string Name { get; set; }public int Age { get; set; }}public class Cat : Animal{public Cat(string catName, int catAge){Name = catName;Age = catAge;}}
現在,我們想比較基類Animal的兩個實例,為此,有必要專門寫一個類讓他實現IComparer<Animal>接口。
public class AnimalSizeComparator : IComparer<Animal>{public int Compare(Animal x, Animal y){if (x != null && y != null){if (x.Age > y.Age){return 1;}else if (x.Age == y.Age){return 0;}else{return -1;}}else{return -1;}}}
在幫助類中的方法中,針對Cat進行比較,方法接收IComparer<Cat>類型的形參。
public class MyHelper{public void CompareCats(IComparer<Cat> catComparer){var cat1 = new Cat("小貓1",1);var cat2 = new Cat("小貓2",2);if (catComparer.Compare(cat2, cat1) > 0){Console.WriteLine("小貓2勝出");}else{Console.WriteLine("小貓1勝出");}}}
有了逆變,客戶端調用MyHelper的CompareCats方法時,可以傳入IComparer<Animal>類型的實參。
IComparer<Animal> animalComparer = new AnimalSizeComparator();MyHelper myHelper = new MyHelper();myHelper.CompareCats(animalComparer);Console.ReadKey();
可見,在方法中基於派生類接口類型的形參,調用該方法的時候可以傳入基類接口類型的實參。
總結:在本篇的場景中,派生類接口替代父類接口,稱之為協變;父類接口代替派生類接口,稱之為逆變。