一個簡單例子幫助你理解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>,這個父接口轉換爲子接口的過程就是逆變,程序運行結果如下:
在這裏插入圖片描述

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