10分鍾淺談泛型協變與逆變


首先聲明,本文寫的有點粗糙,只讓你了解什么是協變和逆變,沒有深入研究,根據這些年的工作經驗,發現我們在開發過程中,很少會自己去寫逆變和協變,因為自從net 4.0 (Framework 3.0) 以后,.net 就為我們提供了 定義好的逆變與協變。我們只要會使用就可以。協變和逆變都是在泛型中使用的。

  • 什么是逆變與協變呢

 

可變性是以一種類型安全的方式,將一個對象當做另一個對象來使用。如果不能將一個類型替換為另一個類型,那么這個類型就稱之為:不變量。協變和逆變是兩個相互對立的概念:

 

  • 如果某個返回的類型可以由其派生類型替換,那么這個類型就是支持協變
  • 如果某個參數類型可以由其基類替換,那么這個類型就是支持逆變的。

 

看起來你有點繞,我們先准備個“”鳥”類,在准備一個“麻雀”類,讓麻雀繼承鳥類,一起看代碼研究

 

  /// <summary>
    ////// </summary>
    public class Bird
    {
        public int Id { get; set; }
    }
    /// <summary>
    /// 麻雀
    /// </summary>
    public class Sparrow : Bird
    {
        public string Name { get; set; }
    }

 

 我們分別取實例化這個類,發現程序是能編譯通過的。

Bird bird1 = new Bird();
Bird bird2 = new Sparrow();
Sparrow sparrow1 = new Sparrow();

  //Sparrow sparrow2 = new Bird();//這個是編譯不通過的,違反了繼承性。

但是我們放在集合中,去實例化,是無法通過的

List<Bird> birdList1 = new List<Bird>();

//List<Bird> birdList2 = new List<Sparrow>();//不是父子關系,沒有繼承關系
//一群麻雀一定是一群鳥

 那么我們如何去實現在泛型中的繼承性呢??這就引入了協變和逆變得概念,為了保證類型的安全,C#編譯器對使用了 out 和 in 關鍵字的泛型參數添加了一些限制:

  • 支持協變(out)的類型參數只能用在輸出位置:函數返回值、屬性的get訪問器以及委托參數的某些位置
  • 支持逆變(in)的類型參數只能用在輸入位置:方法參數或委托參數的某些位置中出現。
  • 協變

  我們來看下Net  “System.Collections.Generic”命名空間下的IEnumerable泛型 接口,會發現他的泛型參數使用了out 

現在我們使用下 IEnumerable  接口來進行一下上述實力,會發現,我們的泛型有了繼承關系。

IEnumerable<Bird> birdList1 = new List<Bird>();

IEnumerable<Bird> birdList2 = new List<Sparrow>();//協變
//一群麻雀一定是一群鳥

下面我們來自己定義一個協變泛型接口ICustomerListOut<Out T>,讓 CustomerListOut 泛型類繼承CustomerListOut<Out T> 泛型接口。

代碼如下

    /// <summary>
    /// out 協變 只能是返回結果
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface ICustomerListOut<out T>
    {
        T Get();

       // void Show(T t);//T不能作為傳入參數
    }

    /// <summary>
    /// 類沒有協變逆變
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class CustomerListOut<T> : ICustomerListOut<T>
    {
        public T Get()
        {
            return default(T);
        }

        public void Show(T t)
        {

        }
    }

 我們會發現,在泛型斜變的時候,泛型不能作為方法的參數。我們用自己定義的泛型接口和泛型類進行實例化試試,我們會發現編譯通過

ICustomerListOut<Bird> customerList1 = new CustomerListOut<Bird>();//這是能編譯的
ICustomerListOut<Bird> customerList2 = new CustomerListOut<Sparrow>();//這也是能編譯的,在泛型中,子類指向父類,我們稱為協變

到這里協變我們就學完了,協變就是讓我們的泛型有了子父級的關系。本文開始的時候,協變和逆變,是在C# 4.0 以后才有的,那C# 4.0以前我們是怎么寫的呢,那個時候沒有協變?

老版本的寫法

  List<Bird> birdList3 = new List<Sparrow>().Select(c => (Bird)c).ToList();//4.0以前的寫法

等學完逆變,本文列出C# 4.0 以后的版本 中framework 已經定義好的協變、逆變 泛型接口,泛型類,泛型委托。

  • 逆變

 剛才我們學習了泛型參數用out 去修飾,餃子協變,現在來學習下逆變,逆變是使用in來修飾的

這里就是Net 4.0 給我們提供的逆變寫法

我們自己寫一個逆變的接口  ICustomerListIn<in T> ,在寫一個逆變的 泛型類 CustomerListIn<T>:ICustomerListIn<T> ,代碼如下

    /// <summary>
    /// 逆變
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface ICustomerListIn<in T>
    {
        //T Get();//不能作為返回值

        void Show(T t);
    }

    public class CustomerListIn<T> : ICustomerListIn<T>
    {
        public T Get()
        {
            return default(T);
        }

        public void Show(T t)
        {
        }
    }

 

 逆變的泛型參數是不能作為泛型方法的返回值的,我們來看下實例化鳥類,和麻雀類,看好使不好使。

ICustomerListIn<Sparrow> customerList2 = new CustomerListIn<Sparrow>();
ICustomerListIn<Sparrow> customerList1 = new CustomerListIn<Bird>();//父類指向子類,我們稱為逆變

ICustomerListIn<Bird> birdList1 = new CustomerListIn<Bird>();
birdList1.Show(new Sparrow());
birdList1.Show(new Bird());

Action<Sparrow> act = new Action<Bird>((Bird i) => { });

 到此我們就完全學完了逆變與協變

 

  • 總結

逆變與協變只能放在泛型接口和泛型委托的泛型參數里面,

在泛型中out修飾泛型稱為協變,協變(covariant  修飾返回值 ,協變的原理是把子類指向父類的關系,拿到泛型中。
 在泛型中in 修飾泛型稱為逆變, 逆變(contravariant )修飾傳入參數,逆變的原理是把父類指向子類的關系,拿到泛型中。

  • NET 中自帶的斜變逆變泛型

 序號  類別  名稱
 1  接口  IEnumerable<out T> 
 2 委托  Action<in T>
3 委托 Func<out TResult>
 4  接口 IReadOnlyList<out T> 
 5  接口 IReadOnlyCollection<out T>
     

 各位朋友,如果誰還知道,請留言告知

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM