文章出處

 

     C#4.0中有一個新特性:協變與逆變。可能很多人在開發過程中不常用到,但是深入的了解他們,肯定是有好處的。

     協變和逆變體現在泛型的接口和委托上面,也就是對泛型參數的聲明,可以聲明為協變,或者逆變。什么?泛型的參數還能聲明?對,如果有了參數的聲明,則該泛型接口或者委托稱為“變體”。

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

     顯然,上面那段代碼是會報錯的, 雖然汽車繼承于車子,可以隱士轉換為車子,但是List<汽車>并不繼承于List<車子>,所以上面的轉換,是行不通的。

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

然而這樣卻是可以的。那么IEnumerable接口有什么不同呢,我們且看編譯器的提示:

我們可以看到,泛型參數的,用了一個“out”關鍵字作為聲明。看來,關鍵是這個在起作用了。

 “協變”是指能夠使用與原始指定的派生類型相比,派生程度更大的類型。 

 “逆變”則是指能夠使用派生程度更小的類型。逆變,逆于常規的變。

協變和逆變,使用“out”,和“in”兩個關鍵字。但是只能用在接口和委托上面,對泛型的類型進行聲明

當聲明為“out”時,代表它是用來返回的,只能作為結果返回,中途不能更改。

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

回到上面的例子,正因為“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”關鍵字,聲明為逆變,讓參數去接受一個相對更“弱“的類型,其實是讓一個參數的類型,更加具體化,更明確化的一個過程。

 

 

  

 

 


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()