本人一向不喜欢讲太多理论,长篇大论的让人烦躁。下面我们就通过一个简单的小例子来快速理解协变和逆变。
类的继承和派生
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>
,这个父接口转换为子接口的过程就是逆变,程序运行结果如下: