協變和逆變
開發時經常與到以下的問題首先看代碼:
定義一個水果類和繼承了該類的蘋果類
public class Fruit
{
public string Name { get; set; }
}
public class Apple : Fruit
{
}
有一個方法接收一個元素類型為Fruit的泛型集合
如下所示
static void Output(List fruits)
{
foreach (Fruit f in fruits)
ConsoleWriteLine(fName);
}
由於Apple類繼承自Fruit
所以很自然的認為以下代碼
應該
能夠正常運行
static void Main(string[] args)
{
List apples = new List();
Output(apples);
ConsoleReadLine();
}
但實際上在
NET Framework
以前的版本中這段代碼不能通過編譯
還有另外一種相似的情況
在Windows窗體應用程序中鼠標點擊事件和鍵盤按鍵事件擁有不同類型的事件參數MouseEventArgs和KeyPressEventArgs
這兩個類均繼承自EventArgs
如果希望在這兩件事件觸發時執行相同的操作
期望編寫以下
通用
的事件處理程序附加到兩個事件上是行不通的
private void Form_UserAction(object sender EventArgs e)
{
}
只能須創建兩個單獨的事件處理程序來執行操作
Visual C# 中引入的協變和逆變解決了類似於這樣的問題
在泛型接口和委托中協變(covariance)可以使用泛型參數所定義類型的繼承類型逆變(contravariance)用於使用更一般的類型一個泛型接口或委托的泛型參數被聲明為協變或逆變時該接口或委托稱為變體在NET Framework 和Visual Studio 中C#和Visual Basic均支持變體泛型接口和委托並且允許泛型參數的隱式轉換而且這兩種語言都允許創建自定義變體接口和委托變體只支持引用類型值類型不支持變體
使用協變第一個問題可以解決這些代碼在Visual Studio 中能夠正確編譯並運行使用逆變可以解決第二個問題這時事件處理程序使用了更一般的類型(該事件的委托允許使用更一般的類型)
接口中的變體
在NET Framework 中對一些已存在的泛型接口引入了變體支持這支持實現了這些接口的類的隱式轉換
這些接口是
IEnumberable
IEnumerator
IQueryable
IGrouping
IComparer
IEqualityComparer
IComparable
開發人員還可以在泛型類型參數上使用in和out關鍵字以聲明變體泛型接口
使用out關鍵字聲明協變泛型參數例如以下代碼
interface IFileCollection
{ }
但是該變體類型T必須遵守以下規則
該類型不能作為方法參數而只能作為返回類型
interface IFileCollection
{
T IndexOf(int i);
}
第一個規則有一個特殊情況是當方法參數是逆變泛型委托時可以將該類型作為該委托的泛型類型參數
interface IFileCollection
{
void Delete(Action file);
}
該類型不能作為接口方法中泛型類型的約束例如以下代碼是錯誤的
interface IFileCollection
{
void Display where R : T;
}
使用in關鍵字聲明逆變泛型參數
逆變類型僅能用於方法的參數和泛型類型約束而不能作為返回類型
interface IOperator
{
void Increace(T value);
void Double() where R : T;
}
可以在一個接口中同時使用out和in定義協變和逆變但仍需遵守相應規則
實現變體接口時語法與普通接口語法一致但實現了變體接口的類不在是變體的如果某個接口繼承自變體接口根據需要使用in或out來指定子接口是否仍然為變體類型如果某個接口同時繼承了變體接口和非變體接口那麼該接口為非變體類型並且不能從逆變接口繼承為協變接口
委托中的變體
NET Framework 中為某些已存在的泛型委托引入變體支持這些支持在使用委托類型匹配方法簽名時提供了很大的靈活性這些委托是
System命名空間下的Action委托例如Action和Action
System命名空間下的Func委托例如Func和Func
Predicate委托
Comparison委托
EventHandler委托(正是由於該委托的存在解決了我們的第個問題)
Converter委托
同樣可以使用out和in關鍵字定義協變和逆變泛型參數仍然需要遵守在接口中定義時相應的規則定義完成之後使用原來的委托訪問語法實例化和調用委托即可
總結
Visual C# 中新提供了協變和逆變的新特性一個泛型接口或委托的泛型參數被聲明為協變或逆變時該接口或委托稱為變體這為我們解決類似於開篇中的兩類問題帶來了便利NET Framework 中已為現有的一些接口和委托增加了變體支持並且開發人員可以使用in和out關鍵字定義自己的變體接口和委托但在定義時需要遵守相應的規則
From:http://tw.wingwit.com/Article/program/net/201311/11396.html