新聞中心
現(xiàn)在我們接著來(lái)談?wù)凜#4.0中一個(gè)重要的新特性:協(xié)變(Covariance)與逆變(Contravariance)。對(duì)于協(xié)變與逆變,大家肯定不會(huì)感到陌生,但是我相信有很多人不能很清晰地說(shuō)出他們之間的區(qū)別。我希望通過(guò)這篇文章能夠讓讀者更加深刻的認(rèn)識(shí)協(xié)變與逆變。但是也不排除另一種可能,那就是讀者這篇文章你對(duì)這兩個(gè)概念更加模糊。文章一些內(nèi)容僅代表個(gè)人觀點(diǎn),如有不妥,還望指正。

長(zhǎng)海ssl適用于網(wǎng)站、小程序/APP、API接口等需要進(jìn)行數(shù)據(jù)傳輸應(yīng)用場(chǎng)景,ssl證書(shū)未來(lái)市場(chǎng)廣闊!成為成都創(chuàng)新互聯(lián)的ssl證書(shū)銷(xiāo)售渠道,可以享受市場(chǎng)價(jià)格4-6折優(yōu)惠!如果有意向歡迎電話(huà)聯(lián)系或者加微信:13518219792(備注:SSL證書(shū)合作)期待與您的合作!
一、兩個(gè)概念:強(qiáng)類(lèi)型與弱類(lèi)型
為了后面敘述方便,我現(xiàn)在這里自定義兩個(gè)概念:強(qiáng)類(lèi)型和弱類(lèi)型。在本篇文章中,強(qiáng)類(lèi)型和弱類(lèi)型指的是兩個(gè)具有直接或者間接繼承關(guān)系的兩個(gè)類(lèi)。如果一個(gè)類(lèi)是另一個(gè)類(lèi)的直接或者間接基類(lèi),那么它為弱類(lèi)型,直接或者間接子類(lèi)為強(qiáng)類(lèi)型。后續(xù)的介紹中會(huì)用到的兩個(gè)類(lèi)Foo和Bar先定義在這里。Bar繼承自Foo。Foo是弱類(lèi)型,而B(niǎo)ar則是強(qiáng)類(lèi)型。
- public class Foo
- {
- //Others Members...
- }
- public class Bar:Foo
- {
- //Others Members...
- }
有了強(qiáng)類(lèi)型和弱類(lèi)型的概念,我們就可以這樣的定義協(xié)變和逆變:如果類(lèi)型TBar是基于強(qiáng)類(lèi)型Bar的類(lèi)型(比如類(lèi)型參數(shù)為Bar的泛型類(lèi)型,或者是參數(shù)/返回值類(lèi)型為Bar的委托),而類(lèi)型TFoo是基于弱類(lèi)型Foo的類(lèi)型,協(xié)變就是將TBar類(lèi)型的實(shí)例賦值給TFoo類(lèi)型的變量,而逆變則是將TFoo類(lèi)型的實(shí)例賦值給TBar類(lèi)型的變量。
二、委托中的協(xié)變與逆變的使用
協(xié)變和逆變主要體現(xiàn)在兩個(gè)地方:接口和委托,先來(lái)看看在委托中如何使用協(xié)變和逆變?,F(xiàn)在我們定義了如下一個(gè)表示無(wú)參函數(shù)的泛型委托Function ,類(lèi)型參數(shù)為函數(shù)返回值的類(lèi)型。泛型參數(shù)之前添加了一個(gè)out關(guān)鍵字表示T是一個(gè)協(xié)變變體。那么在使用過(guò)程中,基于強(qiáng)類(lèi)型的委托Fucntion 實(shí)例就可以賦值給基于弱類(lèi)型的委托Fucntion 變量。
- public delegate T Function
(); - class Program
- {
- static void Main()
- {
- Function funcBar = new Function (GetInstance);
- Function funcFoo = funcBar;
- Foo foo = funcFoo();
- }
- static Bar GetInstance()
- {
- return new Bar();
- }
- }
接下來(lái)介紹逆變委托的用法。下面定義了一個(gè)名稱(chēng)為Operate的泛型委托,接受一個(gè)具有泛型參數(shù)類(lèi)型的參數(shù)。在定義泛型參數(shù)前添加了in關(guān)鍵字,表示T是一個(gè)基于逆變的變體。由于使用了逆變,我們就可以將基于弱類(lèi)型的委托Operate 實(shí)例就可以賦值給基于強(qiáng)類(lèi)型的委托Operate 變量。
- public delegate void Operate
(T instance); - class Program
- {
- static void Main()
- {
- Operate opFoo = new Operate (DoSth);
- Operate opBar = opFoo;
- opBar(new Bar());
- }
- static void DoSth(Foo foo)
- {
- //Others...
- }
- }
三、接口中的協(xié)變與逆變的使用
接下來(lái)我們同樣通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明在接口中如何使用協(xié)變和逆變。下面定義了一個(gè)繼承自 IEnumerable 接口的IGroup 集合類(lèi)型,和上面一樣,泛型參數(shù)T之前的out關(guān)鍵字表明這是一個(gè)協(xié)變。既然是協(xié)變,我們就可以將一個(gè)基于強(qiáng)類(lèi)型的委托IGroup 實(shí)例就可以賦值給基于弱類(lèi)型的委托IGroup 變量。
- public interface IGroup
: IEnumerable - { }
- public class Group : List , IGroup
- { }
- public delegate void Operate
(T instance); - class Program
- {
- static void Main()
- {
- IGroup groupOfBar = new Group ();
- IGroup groupOfFoo = groupOfBar;
- //Others...
- }
- }
下面是一個(gè)逆變接口的例子。首先定義了一個(gè)IPaintable的接口,里面定義了一個(gè)可讀寫(xiě)的Color屬性,便是實(shí)現(xiàn)該接口的類(lèi)型的對(duì)象具有自己的顏色,并可以改變顏色。類(lèi)型Car實(shí)現(xiàn)了該接口。接口IBrush 定義了一把刷子,泛型類(lèi)型需要實(shí)現(xiàn)IPaintable接口,in關(guān)鍵字表明這是一個(gè)逆變。方法Paint用于將指定的對(duì)象粉刷成相應(yīng)的顏色,表示被粉刷的對(duì)象的類(lèi)型為泛型參數(shù)類(lèi)型。Brush 實(shí)現(xiàn)了該接口。由于IBrush 定義成逆變,我們就可以將基于強(qiáng)類(lèi)型的委托IBrush 實(shí)例就可以賦值給基于弱類(lèi)型的委托IBrush 變量。
- public interface IPaintable
- {
- Color Color { get; set; }
- }
- public class Car : IPaintable
- {
- public Color Color { get; set; }
- }
- public interface IBrush
where T : IPaintable - {
- void Paint(T objectToPaint, Color color);
- }
- public class Brush : IBrush where T : IPaintable
- {
- public void Paint(T objectToPaint, Color color)
- {
- objectToPaint.Color = color;
- }
- }
- class Program
- {
- static void Main()
- {
- IBrush brush = new Brush ();
- IBrush carBrush = brush;
- Car car = new Car();
- carBrush.Paint(car, Color.Red);
- Console.WriteLine(car.Color.Name);
- }
- }
四、從Func 看協(xié)變與逆變的本質(zhì)
接下來(lái)我們來(lái)談?wù)剠f(xié)變和逆變的本質(zhì)區(qū)別是什么。在這里我們以我們非常熟悉的一個(gè)委托Func 作為例子,下面給出了該委托的定義。我們可以看到Func 定義的兩個(gè)泛型參數(shù)分別屬于逆變和協(xié)變。具體來(lái)說(shuō)輸入?yún)?shù)類(lèi)型為逆變,返回值類(lèi)型為協(xié)變。
- public delegate TResult Func
(T arg);
再重申以下這句話(huà)“輸入?yún)?shù)類(lèi)型為逆變,返回值類(lèi)型為協(xié)變”。然后,你再想想為什么逆變用in關(guān)鍵字,而協(xié)變用out關(guān)鍵字。這兩個(gè)不是偶然,實(shí)際上我們可以將協(xié)變/逆變與輸出/輸入匹配起來(lái)。
我們?cè)購(gòu)牧硪粋€(gè)角度來(lái)理解協(xié)變與逆變。我們知道接口代表一種契約,當(dāng)一個(gè)類(lèi)型實(shí)現(xiàn)一個(gè)接口的時(shí)候就相當(dāng)于簽署了這份契約,所以必須是實(shí)現(xiàn)接口中所有的成員。實(shí)際上類(lèi)型繼承也屬于一種契約關(guān)系,基類(lèi)定義契約,子類(lèi)“簽署”該契約。對(duì)于類(lèi)型系統(tǒng)來(lái)說(shuō),接口實(shí)現(xiàn)和類(lèi)型繼承本質(zhì)上是一致的。契約是弱類(lèi)型,簽署這份契約的是強(qiáng)類(lèi)型。
將契約的觀點(diǎn)應(yīng)用在委托上面,委托實(shí)際上定義了一個(gè)方法的簽名(參數(shù)列表和返回值),那么參數(shù)和返回值的類(lèi)型就是契約,現(xiàn)在的關(guān)鍵是誰(shuí)去履行這份契約。所有參數(shù)是外界傳入的,所以基于參數(shù)的契約履行者來(lái)源于外部,也就是被賦值變量的類(lèi)型,所以被賦值變量類(lèi)型是強(qiáng)類(lèi)型。而對(duì)于代理本身來(lái)說(shuō),參數(shù)是一種輸入,也就是一種采用in關(guān)鍵字表示的逆變。
而對(duì)于委托的返回值,這是給外部服務(wù)的,是委托自身對(duì)外界的一種承諾,所以它自己是契約的履行著,因此它自己應(yīng)該是強(qiáng)類(lèi)型。相應(yīng)地,對(duì)于代理本身來(lái)說(shuō),返回值是一種輸出,也就是一種采用out關(guān)鍵字定義的協(xié)變。
也正式因?yàn)檫@個(gè)原因,對(duì)于一個(gè)委托,你不能將參數(shù)類(lèi)型定義成成協(xié)變,也不能將返回類(lèi)型定義成逆變。下面兩中變體定義方式都是不能通過(guò)編譯的。
- delegate TResult Fucntion
(T arg); - delegate TResult Fucntion in TResult>(T arg);
說(shuō)到這里,我想有人要問(wèn)一個(gè)問(wèn)題,既然輸入表示逆變,輸出表示協(xié)變,委托的輸出參數(shù)應(yīng)該定義成協(xié)變了?非也,實(shí)際上輸出參數(shù)在這里既輸出輸出,也輸出輸入(畢竟調(diào)用的時(shí)候需要指定一個(gè)對(duì)應(yīng)類(lèi)型的對(duì)象)。也正是為此,輸出參數(shù)的類(lèi)型及不能定義成協(xié)變,也不能定義成逆變。所以下面兩種變體的定義也是不能通過(guò)編譯的。
- delegate void Action
(out T arg); - delegate void Action
(out T arg);
雖然這里指介紹了關(guān)于委托的協(xié)變與逆變,上面提到的契約和輸入/輸出的關(guān)系也同樣適用于基于接口的協(xié)變與逆變。你自己可以采用這樣的方式去分析上面一部分我們定義的IGroup 和IBrush 。
五、逆變實(shí)現(xiàn)了“算法”的重用
實(shí)際上關(guān)系協(xié)變和逆變體現(xiàn)出來(lái)的編程思想,還有一種我比較推崇的說(shuō)法,那就是:協(xié)變是繼承的體現(xiàn),而逆變體現(xiàn)的則是多態(tài)。實(shí)際上這與上面分析的契約關(guān)系本質(zhì)上是一致的。
關(guān)于逆變,在這里請(qǐng)容我再啰嗦一句:逆變背后蘊(yùn)藏的編程思想體現(xiàn)出了對(duì)算法的重用——我們?yōu)榛?lèi)定義了一套操作,可以自動(dòng)應(yīng)用于所有子類(lèi)的對(duì)象。
分享題目:C#4.0新特性:協(xié)變與逆變中的編程思想
URL鏈接:http://fisionsoft.com.cn/article/djgoodd.html


咨詢(xún)
建站咨詢(xún)
