一个简单例子帮助你理解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)
        {
            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>,这个父接口转换为子接口的过程就是逆变,程序运行结果如下:
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章