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方法,完全的類型安全轉換。