10分钟浅谈泛型协变与逆变
首先聲明,本文寫的有點粗糙,只讓你了解什么是協(xié)變和逆變,沒有深入研究,根據(jù)這些年的工作經(jīng)驗,發(fā)現(xiàn)我們在開發(fā)過程中,很少會自己去寫逆變和協(xié)變,因為自從net 4.0 (Framework 3.0)以后,.net就為我們提供了定義好的逆變與協(xié)變。我們只要會使用就可以。協(xié)變和逆變都是在泛型中使用的。
什么是逆變與協(xié)變呢
可變性是以一種類型安全的方式,將一個對象當做另一個對象來使用。如果不能將一個類型替換為另一個類型,那么這個類型就稱之為:不變量。協(xié)變和逆變是兩個相互對立的概念:
如果某個返回的類型可以由其派生類型替換,那么這個類型就是支持協(xié)變的
如果某個參數(shù)類型可以由其基類替換,那么這個類型就是支持逆變的。
看起來你有點繞,我們先準備個“”鳥”類,在準備一個“麻雀”類,讓麻雀繼承鳥類,一起看代碼研究
/// <summary>
/// 鳥
/// </summary>
public class Bird
{
public int Id { get; set; }
}
/// <summary>
/// 麻雀
/// </summary>
public class Sparrow : Bird
{
public string Name { get; set; }
}
我們分別取實例化這個類,發(fā)現(xiàn)程序是能編譯通過的。
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>();//不是父子關(guān)系,沒有繼承關(guān)系
//一群麻雀一定是一群鳥
那么我們?nèi)绾稳崿F(xiàn)在泛型中的繼承性呢??這就引入了協(xié)變和逆變得概念,為了保證類型的安全,C#編譯器對使用了out和in關(guān)鍵字的泛型參數(shù)添加了一些限制:
支持協(xié)變(out)的類型參數(shù)只能用在輸出位置:函數(shù)返回值、屬性的get訪問器以及委托參數(shù)的某些位置
支持逆變(in)的類型參數(shù)只能用在輸入位置:方法參數(shù)或委托參數(shù)的某些位置中出現(xiàn)。
協(xié)變
我們來看下Net “System.Collections.Generic”命名空間下的IEnumerable泛型 接口,會發(fā)現(xiàn)他的泛型參數(shù)使用了out
現(xiàn)在我們使用下IEnumerable 接口來進行一下上述實力,會發(fā)現(xiàn),我們的泛型有了繼承關(guān)系。
IEnumerable<Bird> birdList1 = new List<Bird>();
IEnumerable<Bird> birdList2 = new List<Sparrow>();//協(xié)變
//一群麻雀一定是一群鳥
下面我們來自己定義一個協(xié)變泛型接口ICustomerListOut<Out T>,讓CustomerListOut泛型類繼承CustomerListOut<Out T>泛型接口。
代碼如下
/// <summary>
/// out 協(xié)變 只能是返回結(jié)果
/// </summary>
/// <typeparam name="T"></typeparam>
public interface ICustomerListOut<out T>
{
T Get();
// void Show(T t);//T不能作為傳入?yún)?shù)
}
/// <summary>
/// 類沒有協(xié)變逆變
/// </summary>
/// <typeparam name="T"></typeparam>
public class CustomerListOut<T> : ICustomerListOut<T>
{
public T Get()
{
return default(T);
}
public void Show(T t)
{
}
}
我們會發(fā)現(xiàn),在泛型斜變的時候,泛型不能作為方法的參數(shù)。我們用自己定義的泛型接口和泛型類進行實例化試試,我們會發(fā)現(xiàn)編譯通過
ICustomerListOut<Bird> customerList1 = new CustomerListOut<Bird>();//這是能編譯的
ICustomerListOut<Bird> customerList2 = new CustomerListOut<Sparrow>();//這也是能編譯的,在泛型中,子類指向父類,我們稱為協(xié)變
到這里協(xié)變我們就學(xué)完了,協(xié)變就是讓我們的泛型有了子父級的關(guān)系。本文開始的時候,協(xié)變和逆變,是在C# 4.0以后才有的,那C# 4.0以前我們是怎么寫的呢,那個時候沒有協(xié)變?
老版本的寫法
List<Bird> birdList3 = new List<Sparrow>().Select(c => (Bird)c).ToList();//4.0以前的寫法
等學(xué)完逆變,本文列出C# 4.0以后的版本中framework已經(jīng)定義好的協(xié)變、逆變 泛型接口,泛型類,泛型委托。
逆變
剛才我們學(xué)習(xí)了泛型參數(shù)用out去修飾,餃子協(xié)變,現(xiàn)在來學(xué)習(xí)下逆變,逆變是使用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)
{
}
}
逆變的泛型參數(shù)是不能作為泛型方法的返回值的,我們來看下實例化鳥類,和麻雀類,看好使不好使。
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) => { });
到此我們就完全學(xué)完了逆變與協(xié)變
總結(jié)
逆變與協(xié)變只能放在泛型接口和泛型委托的泛型參數(shù)里面,
在泛型中out修飾泛型稱為協(xié)變,協(xié)變(covariant) 修飾返回值 ,協(xié)變的原理是把子類指向父類的關(guān)系,拿到泛型中。
在泛型中in 修飾泛型稱為逆變, 逆變(contravariant)修飾傳入?yún)?shù),逆變的原理是把父類指向子類的關(guān)系,拿到泛型中。
NET中自帶的斜變逆變泛型
| 序號 | 類別 | 名稱 |
| 1 | 接口 | IEnumerable<out T> |
| 2 | 委托 | Action<in T> |
| 3 | 委托 | Func<out TResult> |
| 4 | 接口 | IReadOnlyList<out T> |
| 5 | 接口 | IReadOnlyCollection<out T> |
各位朋友,如果誰還知道,請留言告知
總結(jié)
以上是生活随笔為你收集整理的10分钟浅谈泛型协变与逆变的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 红警Online手游空指部有什么用?空指
- 下一篇: tc397的mcal和tc275的mca