本人一向不喜歡講太多理論,長篇大論的讓人煩躁。下面我們就通過一個簡單的小例子來快速理解協變和逆變。
類的繼承和派生
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Apple apple = new Apple();
Fruit fruit = apple;
fruit.Print();
}
}
public class Fruit
{
public virtual void Print()
{
Console.WriteLine("Fruit");
}
}
public class Apple : Fruit
{
public override void Print()
{
Console.WriteLine("Apple");
}
}
}
上面的這段代碼應該很好理解,父類Fruit
派生出子類Apple
,主函數裏將Apple的實例轉換爲Fruit的實例,一切看起來都是那麼的順理成章,這是因爲:子類對象可以隱式轉換爲父類對象。
協變
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<Apple> apples = new List<Apple>();
apples.Add(new Apple());
apples.Add(new Apple());
// 這裏會報錯,下面的語句不會執行
List<Fruit> fruits = apples;
foreach (Fruit fruit in fruits)
{
fruit.Print();
}
}
}
public class Fruit
{
public virtual void Print()
{
Console.WriteLine("Fruit");
}
}
public class Apple : Fruit
{
public override void Print()
{
Console.WriteLine("Apple");
}
}
}
很可惜,上面的這段代碼會報錯!報錯的原因也很簡單:Fruit
類和Apple
類確實是父類和子類的關係,但List<Fruit>
和List<Apple>
卻不是父類和子類的關係。這就好比有人跟你說:貓是動物,但一羣貓不是一羣動物!這讓我們理解起來有些困難,所以C#裏引入了協變。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<Apple> apples = new List<Apple>();
apples.Add(new Apple());
apples.Add(new Apple());
// 這裏不會報錯,下面的語句可以執行
IEnumerable<Fruit> fruits = apples;
foreach (Fruit fruit in fruits)
{
fruit.Print();
}
}
}
public class Fruit
{
public virtual void Print()
{
Console.WriteLine("Fruit");
}
}
public class Apple : Fruit
{
public override void Print()
{
Console.WriteLine("Apple");
}
}
}
運行結果如下:
逆變
協變可以看做是子類泛型轉換爲父類泛型,那麼父類泛型能否轉換爲子類泛型呢?答案當然也是可以的,C#中的逆變就可以幫我們實現。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
IComparer<Fruit> compare = new CompareRole();
Compare(compare);
}
static void Compare(IComparer<Apple> compare)
{
Apple x = new Apple();
x.Name = "蘋果一";
x.Weight = 30;
Apple y = new Apple();
x.Name = "蘋果二";
y.Weight = 20;
int result = compare.Compare(x, y);
if (result == -1)
{
Console.WriteLine("蘋果一重量小於蘋果二");
}
else if (result == 0)
{
Console.WriteLine("蘋果一重量等於蘋果二");
}
else
{
Console.WriteLine("蘋果一重量大於蘋果二");
}
}
}
public class Fruit
{
public string Name { get; set; }
public double Weight { get; set; }
}
public class Apple : Fruit
{
}
public class CompareRole : IComparer<Fruit>
{
public int Compare(Fruit x, Fruit y)
{
if (x == null || y == null)
{
return -1;
}
if (x.Weight < y.Weight)
{
return -1;
}
else if (x.Weight == y.Weight)
{
return 0;
}
else
{
return 1;
}
}
}
}
上面的代碼用於比較兩個蘋果的重量。我們可以發現:public class CompareRole : IComparer<Fruit>
表明該類實現了接口IComparer<Fruit>
,但static void Compare(IComparer<Apple> compare)
卻表明該函數只接受一個IComparer<Apple>
參數,但事實上在主函數裏我們確實把IComparer<Fruit>
的實例賦值給了IComparer<Apple>
,這個父接口轉換爲子接口的過程就是逆變,程序運行結果如下: