泛型協變與抗變(二)

小世界的野孩子發表於2019-07-25

前言

  在.NET 4之前,泛型介面是不變的。.NET 4通過協變和抗變為泛型介面和泛型委託新增了一個重要的擴充套件。協變和抗變指對引數和返回值的型別進行轉換。

  我們來看下到底什麼是協變什麼是抗變:
  如果某個返回的型別可以由其基類替換,那麼這個型別就是支援協變的
  如果某個引數型別可以由其派生類替換,那麼這個型別就是支援逆變(抗變)的。

函式的型別轉換

  在理解協變與抗變之前,我們看下面這個例子:

  class Program
     {
        public static string Tmain(object o)
        {
            return "aaa";
        }

        static void Main(string[] args)
        {
            string a = "aaa";
            object b = Tmain(a);
        }

     }

 

  我們仔細看下這個傳值和返回。注意其中發現了兩次隱式轉換。

    1、向函式傳值的時候 引數a從string型別轉換成object型別

    2、最後接收返回值的時候b由string型別轉換成object型別

  我們在返回函式來看。

    1、 String Tmain(object o) 可以轉換成string Tmain(string o)

    2、 String Tmain(string o) 可以轉換成 object Tmain(string o)

  在這裡,也就是說函式輸入的時候輸入型別可以從object轉換成string。基類-派生類

  在函式輸出時,函式的輸出型別(返回型別)從string轉換成object。派生類-基類。

  這裡就比較接近泛型介面的協變和抗變的概念了。我們再看我們開頭的概念

 

  如果某個返回的型別可以由其基類替換,那麼這個型別就是支援協變的
  如果某個引數型別可以由其派生類替換,那麼這個型別就是支援逆變(抗變)的。

 

理解泛型介面的協變和抗變(in、out)

  我們下面來看看泛型介面的協變及抗變的例子:

  首先我們看下協變,在C#高階程式設計(第十一版)中指出,如果泛型型別用out關鍵字標註,泛型介面就是協變的。這也就意味著返回型別只能是T。

    /// <summary>
    /// 標識out,意味著返回型別只能是T
    /// </summary>
    /// <typeparam name="T"></typeparam>
    interface Itest<out T>
    {
        T Tmain(object value);
    }

 
    public class Test : Itest<string>
    {
        public string Tmain(object value)
        {
            return value.ToString();
        }
 }

 

我們呼叫時:

 static void Main(string[] args)
        {
            Itest<string> itest = new Test();
            Itest<object> itestObj = itest;
        }

 

  在這裡,我們最後接收其返回值的時候,理應由string型別進行接收的,但是這裡我們可以修改,由其基類object型別進行替換。也就是在某個返回型別可以由其基類替換的時候,也就是支援協變了。注意其關鍵點。返回型別、由基類替換派生類。

 

 

  然後我們再看看那抗變也可稱為逆變。在C#高階程式設計中指出的概念:如果泛型型別用in關鍵字標註,泛型介面就是抗變的。這樣,介面只能把泛型型別T用作其方法的輸入。

  /// <summary>
    /// 標識in,意味著輸入型別只能是T
    /// </summary>
    /// <typeparam name="T"></typeparam>
    interface Itest<in T>
    {
        string Tmain(T value);
    }


    public class Test : Itest<object>
    {
        public string Tmain(object value)
        {
            return value.ToString();
        }
    }

 
    class Program
    {
        static void Main(string[] args)
        {
            Itest<object> itest = new Test();
            Itest<string> itestStr= itest;
        }
}

 

 

  這裡我們看上面這個例子,其中返回型別已經是固定的string型別了。而泛型介面中的泛型型別用來作為引數傳遞了。我們再看呼叫時,正常傳入object型別的引數,,但是我們修改傳入引數型別為string型別也是可以的。也就是我們在參入引數時,引數可以由其派生類替換的話,那麼這個類也就是支援抗變(逆變)的。注意其中關鍵點。傳入引數,派生類替換基類。

總結

  其實在上述例子及其概念中,我們可以發現,泛型介面的協變及抗變,也就是將型別引數返回或者傳入的情況,在這情況下進行其型別的隱式轉換所遵循的規律。

    協變:(使用關鍵字out)返回型別可以由其基類所替代的時候,就是支援協變的。

    抗變(逆變):(使用關鍵字in)傳入引數型別可以由其派生類所代替的時候,就是支援抗變(逆變)的。

 

  夫學須也,才須學也,非學無以廣才,非志無以成學-------諸葛亮

 


 

                      c#基礎知識詳解系列

 

  歡迎大家掃描下方二維碼,和我一起學習更多的C#知識

 

 

 

  

 

相關文章