[C#]] 泛型 - 共變 逆變

共變 逆變 傻傻分不清

前言

泛型的 T 是不變性 
你給的類別 無論是 子類 或 父類 都不能取代

基本介紹

C# 的方法
傳回值 支援 共變性Covariance
傳入參數  支援 逆變性Contravariance

    public interface ILevel
    {
        int LevelId { get; set; }
    }

    public class Level1: ILevel
    {
        public int LevelId { get; set; }

        public string Name { get; set; }

        public int test1 { get; set; }
    }

    public class Level2: Level1
    {
        public int test2 { get; set; }
    }

    public class Level3 : Level2
    {
        public int test3 { get; set; }
    }

我寫了 三個類別 繼續繼承下去
 

       public A01B_Covariance()
        {
            //共變性
            Level1 l1 = NewLevel2();
            //Level3 l3 = NewLevel2(); 編譯失敗

            //逆變性
            //GetLevel2(new Level1()); 編譯失敗
            GetLevel2(new Level3());
        }

        private Level2 NewLevel2()
        { 
            return new Level2();
        }

        private void GetLevel2(Level2 a)
        {
        }

共變性 就是 基底類別可取代衍生類別
Level1 l1 = NewLevel2();
因為level2  有  level1的資料 所以  能  當作level1 回傳使用
Level3 l3 = NewLevel2(); 編譯失敗
因為level2 沒有 level3的資料 所以 不能  當作level3 回傳使用

逆變性  就是 衍生類別可取代基底類別
GetLevel2(new Level1()); 編譯失敗
因為level1 沒有 level2的資料 所以 不能 當作level2 傳入參數
GetLevel2(new Level3());
因為level3 有 level2的資料 所以 能 當作level2 傳入參數

泛型的 共變性 逆變性

泛型 T 就是不變性  子類 或 父類 都不能取代
加上out T 就是 共變 Covariance  , 此時的T 就只能是回傳型別
加上in T 就是 逆變 Contravariance  , 此時的T 就只能是傳入型別
程式碼如下

    public interface ILevelGet<out T>
    {
        T Get(int id);
    }

    public interface ILevelAdd<in T>
    {
        void Add(T level);
    }

    public class LevelPool<T> : ILevelGet<T>, ILevelAdd<T>
        where T : class, ILevel, new()

    {
        private List<T> Pool { get; }

        public LevelPool()
        {
            this.Pool = new List<T>();
        }
        public void Add(T level)
        {
            this.Pool.Add(level);
        }

        public T Get(int id)
        {
            var t = this.Pool.SingleOrDefault(x => x.LevelId == id) ?? new T();
            return t;
        }
    }

再來看 使用時的程式碼 先看一下 共變性

        public void testCovariant()
        {
            this.Level2Pools = new LevelPool<Level2>();

            //out 共變性 Covariant
            //基底取代衍生
            GetLevel1(this.Level2Pools);
            GetLevel2(this.Level2Pools);
            //GetLevel3(this.Level2Pools);編譯失敗
        }

        //interface ILevelGet<out T>
        private void GetLevel1(ILevelGet<Level1> repository)
        {
            var level1 = repository.Get(301);
            Console.WriteLine(level1.LevelId);
        }

        private void GetLevel2(ILevelGet<Level2> repository)
        {
            var level2 = repository.Get(301);
            Console.WriteLine(level2.LevelId);
        }

        private void GetLevel3(ILevelGet<Level3> repository)
        {
            var level3 = repository.Get(301);
            Console.WriteLine(level3.LevelId);
        }

接下去是 逆變性

        public void testContravariant()
        {
            this.Level2Pools = new LevelPool<Level2>();

            //in 逆變性 Contravariant
            //衍生取代基底
            //AddLevel1(this.Level2Pools);編譯失敗
            AddLevel2(this.Level2Pools);
            AddLevel3(this.Level2Pools);
        }

        //interface ILevelAdd<in T>
        private void AddLevel1(ILevelAdd<Level1> repository)
        {
            repository.Add(new Level1 { LevelId = 101, Name = "Level1" });
        }

        private void AddLevel2(ILevelAdd<Level2> repository)
        {
            repository.Add(new Level2 { LevelId = 201, Name = "Level2" });
        }

        private void AddLevel3(ILevelAdd<Level3> repository)
        {
            repository.Add(new Level3 { LevelId = 301, Name = "Level3" });
        }

結語

理解了共變 逆變 在泛型設計上 又更多彈性可以運用了

參考連結
C# 4.0:Covariance 與 Contravariance 觀念入門

如果內容有誤請多鞭策謝謝