public interface IEnumerable<out T> : IEnumerable
{
new IEnumerator<T> GetEnumerator();
}
public delegate void Action<in T>(T obj); IEnumerable中的out關鍵字給泛型參數提供了協變的能力,Action中的in關鍵字給泛型參數提供了逆變的能力。 這裡的out和in是相對於誰的入和出?不是相對於接口和委托,而是相對於方法體! 看它們的實現:
class MyEnumerable<T> : IEnumerable<T>
{
public IEnumerator<T> GetEnumerator()
{
yield return default(T);
}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
}
Action<string> myAction = new Action<object>(
(o) =>
{
Console.WriteLine(o.ToString());
});
這樣是不是能看出來泛型參數是怎麼入和出的了? 那麼接口和委托,它們和方法是什麼關系呢,它們兩個之間又是什麼關系,以下純屬個人理解: 接口類型定義了一組方法簽名,委托類型定義了一個方法結構(方法簽名刨除方法名)。 接口實例和委托實例都包含了一組方法入口。 綜上所述,協變與逆變的作用對象是方法體中的泛型參數。 為什麼允許協變與逆變 協變和逆變都是類型發生了轉換,一旦涉及到類型轉換當然就要想類型安全的問題。 協變和逆變之所以可以正常的運轉,就是因為這裡所涉及到的所有類型轉換都是類型安全的! 回頭看最開始的四行代碼: 1 object o1 = "";//類型安全 2 string s1 = (string) o1;//非類型安全 3 IEnumerable<object> o2 = new List<string>();//協變 4 Action<string> s2 = new Action<object>((arg)=>{...});//逆變 顯然第二行的object到string是非類型安全的,那為什麼第四行的object到string就是類型安全的呢? 結合上一個方法體的示例,來看這段代碼:
1 Action<List<int>> myAction = new Action<IList<int>>(
2 (list) =>
3 {
4 Console.WriteLine(list.Count);
5 });
6 myAction(new List<int> {1, 2, 3});
第一行貌似是把IList轉換成了List,但是實際上是這樣的: 第六行傳入的實參是一個List,進入方法體,List被轉換成了IList,然後使用了IList的Count屬性。 所以傳參的時候其實發生的是派生類到基類的轉換,自然也就是類型安全的了。 List<string>到IEnumerable<object>的協變其實也是類似的過程:
1 IEnumerable<Delegate> myEnumerable = new List<Action>
2 {
3 new Action(()=>Console.WriteLine(1)),
4 new Action(()=>Console.WriteLine(2)),
5 new Action(()=>Console.WriteLine(3)),
6 };
7 foreach (Delegate dlgt in myEnumerable)
8 {
9 dlgt.DynamicInvoke();
10 }
實參是三個Action,調用的是Delegate的DynamicInvoke方法,完全的類型安全轉換。