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