国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

不變性、協變性和逆變性(Invariance, Covariance & Contravariance)

2019-11-17 02:19:03
字體:
來源:轉載
供稿:網友

不變性、協變性和逆變性(Invariance, Covariance & Contravariance)

源碼下載

一、里氏替換原則(Liskov Substitution PRinciple LSP)

  我們要講的不是協變性和逆變性(Covariance & Contravariance)嗎?是的,沒錯。但先不要著急,在這之前,我們有必要再回味一下LSP。廢話不多說,直接上代碼:

 1 namespace LSP 2 { 3     public class Bird 4     { 5         public virtual void Show() 6         { 7             Console.WriteLine("It's me, bird."); 8         } 9     }10 }
Bird
 1 namespace LSP 2 { 3     public class Swan : Bird 4     { 5         public override void Show() 6         { 7             Console.WriteLine("It's me, swan."); 8         } 9     }10 }
Swan
 1 namespace LSP 2 { 3     public class Program 4     { 5         static void Main(string[] args) 6         { 7             Bird bird = new Swan(); 8             bird.Show(); 9             Console.ReadLine();10         }11     }12 }
Program

根據里氏替換原則,任何基類可以出現的地方,子類一定可以出現。

因為Swan類繼承于Bird類,所以“Bird bird=new Bird();”中,我需要創建一個Bird對象,你給了我一個Swan對象是完全可行的。通俗地講,我要你提供鳥類動物給我,你給我一只天鵝,當然沒有問題。

然而,我們在調用bird的Show方法時,發生了什么呢?

Bird類和Swan類中都有Show方法,調用這個方法時,編譯器是知道這個bird實際指向的Swan對象的。它會先查看Swan本身是不是有同簽名的方法,如果有就直接調用。如果沒有再往Swan的父類里查看,如果再沒有,再往上面找,直到找到為止。如果最終也沒有找到,就會報錯。

所以,我們看到程序調用的是Swan的Show方法:"It's me, swan."

二、協變和逆變是什么?

關于這個,我們還是先看看官方的解釋:

協變和逆變都是術語,前者指能夠使用比原始指定的派生類型的派生程度更大(更具體的)的類型,后者指能夠使用比原始指定的派生類型的派生程度更小(不太具體的)的類型。

看了是不是有種“懂的依然懂,不懂的依然不懂的感覺”?

簡單地說,

協變:你可以用一個子類對象去替換相應的一個父類對象,這是完全符合里氏替換原則的,和協(諧)的變。如:用Swan替換Bird。

逆變:你可以用一個父類對象去替換相應的一個父類對象,這貌似不符合里氏替原則的,不和協(諧)的逆變。如:用Bird替換Swan。

那么事實真的如此嗎?協變是不是比逆變更合理?其實他們完全就是一回事,都是里氏替換原則的一種表現形式罷了。

三、不變性(Invariance)

我們知道:Bird bird=new Swan();是沒有問題的。

那么對于泛型,List<Bird> birds=List<Swan>();是不是也OK呢?

No!

首先,因為.Net Framework只向泛型接口和委托提供了協變和逆變的便利。

再者,想要實現協變或逆變,也得在語法上注明out(協變)或in(逆變)。

對于這類不支持協變和逆變的情況,我們稱為不變性(Invariance)。為了維持泛型的同質性(Homogeneity),編譯器禁止將List<Swan>隱式或顯式地轉換為List<Bird>。

好了,重點來了!

為什么要這樣?這樣,很不方便。而且,看起來也不符合里氏替換原則。

簡單地說,維持同質性,不允許這樣的轉換,還是為了編譯正常。什么是編譯正常,就是別給咱報錯。

 1 public class Program 2     { 3         public static void Main(string[] args) 4         { 5             List<object> obj = null; 6             List<string> str = null; 7  8             /* Error: 9              * Cannot implicitly convert type10              * 'System.Collections.Generic.List<string>' 11              * to 'System.Collections.Generic.List<object>'12             */13 14             //obj = str;15 16             Console.ReadLine();17         }18     }
VarianceList

如代碼注解的那樣,“obj=str;”編譯器會報錯:

Error :Cannot implicitly convert type 'System.Collections.Generic.List<string>' to 'System.Collections.Generic.List<object>'

List<T>是微軟提供給我們的,里面封閉太多東西,不方便分析,我們就自己動手來寫一個泛型類Invariance<T>。

 1 namespace Invariance 2 { 3     public class Invariance<T> 4     { 5         T Test(T t) 6         { 7             return default(T); 8         } 9     }10 }
Variance<T>

寫好了泛型類,我們再來試一試。

 1 namespace Invariance 2 { 3     public class Program 4     { 5         public static void Main(string[] args) 6         { 7            Invariance<object> invarianceObj = new Invariance<object>(); 8             Invariance<string> invaricaceStr = new Invariance<string>(); 9 10             //invarianceObj = invaricaceStr;11             //invaricaceStr = invarianceObj;12            13             Console.ReadLine();14         }15     }16 }
Variance<T> Test

"invarianceObj = invaricaceStr;"報錯:

Error : Cannot implicitly convert type 'Invariance.Invariance<string>' to 'Invariance.Invariance<object>'

“invaricaceStr = invarianceObj;”報錯:

Error : Cannot implicitly convert type 'Invariance.Invariance<object>' to 'Invariance.Invariance<string>'

講到這么多報錯,還是沒講到核心,為什么要報錯。

我們可以假設,如果不報錯,運行起來會是怎樣:Invariance<T>類型參數T是在使用時,確定具體類型的。

先來說貌似符合里氏替換原則的情況,

Invariance<object> invarianceObj =new Invariance<string>();

用string替換object沒有問題。但這個語句表達的不僅僅是用string來替換object,也表示用object來替換string。

關鍵在于類型參數,是在泛型類中使用的,我們不敢保證他是否于參數還是返回值。

如:Invariance<object> invarianceObj調用Test(object obj),傳入的是自身的類型參數,而實際執行時,是執行實際指向的對象Invariance<string> invarianceStr的Test(string str)方法。很明顯,Invariance<string> invariance的Test(string str)方法需要接收一個string類型的參數,得到卻是一個object。這是不合法的。

那是不是反過來就可以了呢?

Invariance<string> invaricaceStr=new Invariance<object>();

這樣,你實際執行方法時,需要一個object類型的參數,我給你一個string總沒問題了吧。

OK,這樣完全沒有問題。

然而,不要忘了,方法可能不只是有參數,還可能有返回值。

參數:Invariance<string> invaricaceStr調用Test(string str),將string傳給invarianceObj的Test(object obj)方法。目前為止,OK。

返回值:Invariance<string> invaricaceStr要求Test(string str)返回一個string對象。而實際執行方法的invarianceObj卻只能保證返回一個object對象。NG!

看到了吧。這就是為什么.Net Framework要保持類型參數的同質性,而不允許T類型參數,哪怕從子類到父類或父類到子類的任何一種轉換。

因為你只能保證參數或返回值,其中一項轉換成功。

四、協變性(Covariance)

理解了為什么要堅持不變性,理解起協變性就容易多了。如果我能在泛型接口或者委托中保證,我的類型參數,只能外部取出,不允許外部傳入。那么就不存在上面講的將類型參數作為參數傳入方法的情況了。

怎么保證?只需要在類型參數前加out關鍵字就可以了。

1 namespace Covariance2 {3
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 上蔡县| 怀化市| 宣化县| 米易县| 永康市| 密云县| 仲巴县| 海南省| 沾化县| 乾安县| 鄂伦春自治旗| 北安市| 保靖县| 理塘县| 黑河市| 永济市| 丰镇市| 怀安县| 夹江县| 宿州市| 广德县| 革吉县| 汤阴县| 巨鹿县| 察哈| 滨州市| 彰化县| 谷城县| 赤峰市| 高密市| 临武县| 黄平县| 平度市| 三穗县| 清丰县| 廊坊市| 合江县| 馆陶县| 阿克苏市| 巫溪县| 门头沟区|