C#4.0泛型的協變,逆變深刻剖析

 

     C#4.0中有一個新特性:協變與逆變。可能不少人在開發過程當中不經常使用到,可是深刻的瞭解他們,確定是有好處的。spa

     協變和逆變體如今泛型的接口和委託上面,也就是對泛型參數的聲明,能夠聲明爲協變,或者逆變。什麼?泛型的參數還能聲明?對,若是有了參數的聲明,則該泛型接口或者委託稱爲「變體」。code

List<汽車> 一羣汽車 = new List<汽車>();
List<車子> 一羣車子 = 一羣汽車;

     顯然,上面那段代碼是會報錯的, 雖然汽車繼承於車子,能夠隱士轉換爲車子,可是List<汽車>並不繼承於List<車子>,因此上面的轉換,是行不通的。blog

IEnumerable<汽車> 一羣汽車 = new List<汽車>();
IEnumerable<車子> 一羣車子 = 一羣汽車;

然而這樣倒是能夠的。那麼IEnumerable接口有什麼不一樣呢,咱們且看編譯器的提示:繼承

咱們能夠看到,泛型參數的,用了一個「out」關鍵字做爲聲明。看來,關鍵是這個在起做用了。接口

 「協變」是指可以使用與原始指定的派生類型相比,派生程度更大的類型。 開發

 「逆變」則是指可以使用派生程度更小的類型。逆變,逆於常規的變。get

協變和逆變,使用「out」,和「in」兩個關鍵字。可是隻能用在接口和委託上面,對泛型的類型進行聲明編譯器

當聲明爲「out」時,表明它是用來返回的,只能做爲結果返回,中途不能更改。string

當聲明爲"in"時,表明它是用來輸入的,只能做爲參數輸入,不能被返回。it

回到上面的例子,正由於「IEnumerable」接口聲明瞭out,因此,表明參數T只能被返回,中途不會被修改,因此,IEnumerable<車子> 一羣車子 = 一羣汽車;  這樣的強制轉換

是合法的,IL中其實是做了強制轉換的。

 IEnumerable是NET中自帶的,其他還有以下接口和委託:

接口:           
IQueryable<out T> IEnumerator<out T> IGrouping<out TKey,out TElement> IComparer<in T> IEqualityComparer<in T> IComparable<in T> 委託:
System.Action
<in T> System.Func<Out Tresult> Predicate<in T> Comparison<in T> Converter<in TInput,out TOutput>

此外,咱們本身定義泛型接口的時候也能夠使用協變和逆變,咱們不妨來看一個示例,來體現協變的特徵

    interface 接口<out T>
    {
        T 屬性 { get; set; }
    }

我定義一個接口,一個具備get和set訪問器的屬性,然而,編譯是報錯的,提示:變體無效: 類型參數「T」必須爲對於「test.接口<T>.屬性」有效的 固定式。「T」爲 協變。

正由於我聲明瞭T爲協變,因此,T只能被返回,不容許被修改,因此,若是去掉「set」訪問器,才能夠編譯經過。

一樣,若是我在「接口」中聲明一個方法

void 方法(T t);

一樣是會報錯的,T被聲明瞭協變,「方法(T t)」的存在就不可取。

class Program
    {
        static void Main(string[] args)
        {
            接口<汽車> 一羣汽車 = new 類<汽車>();
            接口<車子> 一羣車子 = 一羣汽車;
        }
    }
    interface 接口<out T>
    {
        T 屬性
        {
            get;
        }
    }
    class 類<T> : 接口<T>
    {
        public T 屬性
        {
            get { return default(T); }
        }
    }

上面的代碼是能夠編譯經過的,由於泛型接口「接口」聲明瞭協變,因此「接口<車子> 一羣車子 = 一羣汽車;」是能夠強制轉換成功的,看吧,咱們本身聲明的一樣能夠實現目的。

 若是我把以上的代碼,把「out」改爲「in」呢? 顯然不行,由於聲明「in」規定了T不能被返回,編譯沒法經過的。

然而下面的代碼是正確的:

    interface 接口<in T>
    {
        void 方法(T t);
    }
    class 類<T> : 接口<T>
    {
        public void 方法(T t)
        {
           
        }
    }

聲明「in」不容許被返回,可是能夠進行更改。

接着看:

        static void Main(string[] args)
        {
            接口<車子> 一羣車子 = new 類<車子>();
            接口<汽車> 一羣汽車 = 一羣車子;
        }

啊,這怎麼也能夠啊,「車子」是父類,「汽車」是子類,汽車轉換爲車子正常,車子轉換爲汽車,這樣也行?

其實「車子」也好,「汽車」也好,在這裏都只是泛型參數,並非他們倆之間的轉換,這個基礎的概念必須明白,別繞進去了。
這就是逆變。由於「接口」聲明瞭「in」關鍵字,聲明爲逆變,讓參數去接受一個相對更「弱「的類型,實際上是讓一個參數的類型,更加具體化,更明確化的一個過程。

相關文章
相關標籤/搜索