程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> [C#]淺談協變與逆變

[C#]淺談協變與逆變

編輯:C#入門知識

[C#]淺談協變與逆變


什麼是協變與逆變       協變和逆變都是術語,前者指能夠使用比原始指定的派生類型的派生程度更小(不太具體的)的類型,後者指能夠使用比原始指定的派生類型的派生程度更大(更具體的)的類型。 泛型類型參數支持協變和逆變,可在分配和使用泛型類型方面提供更大的靈活性。 一開始我總是分不清協變和逆變,因為MSDN的解釋實在是嚴謹有余而易讀不足。 其實從中文的字面上來理解這兩個概念就挺容易的了:   "協變"即"協調的轉變","逆變"即"逆向的轉變"。   為什麼說"能夠使用比原始指定的派生類型的派生程度更小(不太具體的)的類型"是協調的,而"能夠使用比原始指定的派生類型的派生程度更大(更具體的)的類型"是逆向的呢,看這兩行代碼:   object o = ""; string s = (string) o; string類型到object類型,也就是派生類到基類,是可以隱式轉換的,因為任何類型向基類的轉換都是類型安全的,所以認為這一轉變是協調的。 object類型到string類型,也就是基類到派生類,就只能是顯式轉換,因為對象o的實際類型不一定是string,強制轉換不是類型安全的,所以認為這一轉變是逆向的。   再看協變與逆變的常見場合:   IEnumerable<object> o = new List<string>();//協變 Action<string> s = new Action<object>((arg)=>{...});//逆變 上例的泛型參數就是分別發生了協調的與逆向的轉變。       協變與逆變的作用對象   從定義中可以看到,協變與逆變都是針對的泛型參數,而且   在.NET Framework 4中,Variant類型參數僅限於泛型接口和泛型委托類型。 為什麼是接口和委托?先看IEnumerable<T>和Action<T>的聲明:  
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方法,完全的類型安全轉換。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved