首先聲明,本文寫的有點粗糙,只讓你瞭解什麼是協變和逆變,沒有深入研究,根據這些年的工作經驗,發現我們在開發過程中,很少會自己去寫逆變和協變,因為自從net 4.0 (Framework 3.0) 以後,.net 就為我們提供了 定義好的逆變與協變。我們只要會使用就可以。協變和逆變都是在泛型中使用的。 ...
首先聲明,本文寫的有點粗糙,只讓你瞭解什麼是協變和逆變,沒有深入研究,根據這些年的工作經驗,發現我們在開發過程中,很少會自己去寫逆變和協變,因為自從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> |
各位朋友,如果誰還知道,請留言告知