文章出處

  這里寫得并不好,重新整理和修改在我的博客中:https://631320085.github.io/2017/02/01/Convariance_Contravariance.html

關鍵字out和in相信大家都不陌生,系統定義的很多泛型類型大家F12都或多或少看見了。但是實際中又很少會用到,以前在紅皮書里看到,兩三頁就介紹完了。有的概念感覺直接搬出來的,只是說這樣寫會怎樣,并沒有形象的將為什么這么設計,什么時候有用。再加上是翻譯的語義很生硬,理解起來很費勁。自然又百度一通,看了一大堆大家各抒己見,這東西還是像一個低分辨率的圖片一樣,不夠清晰。其實現在各種知識點基本都知道大概是怎么回事,怎么用,但是總感覺少點什么,不夠高清。于是最近寫了個控制臺,把各種不夠高清或者需要高清顯示的知識點捋了一邊。果然紙上得來終覺淺,欲知此事寫代碼。好多東西嘗試一遍或者配合使用,的確感覺神清氣爽,很多設計知道了緣由,世界都高清了。廢話不多說,分享下自認為簡單粗暴有效的愚見吧。

 

------------------------------------------------關于定義的驗證-----------------------------------------------------------------------

 

  既然是泛型out in關鍵字(非泛型試過了),那就寫一個吧。于是

 

 

 

  大意就是只能用于接口和委托的泛型,于是這樣寫

  果然OK,為什么只能接口和委托,一時半會想不通,先不管,再捋捋。紅皮書里寫了一堆,不理解沒關系,但是說了一個關鍵點,in修飾的泛型只能是傳入參數,out修飾的只能是返回類型。問題又來了,為什么這么設計,想不通,先不管,驗證下

  果然,接著捋。改正錯誤,現在一個泛型接口帶in,out關鍵字的定義好了。接口能干什么,就是實現它啊。實現泛型接口需要指定泛型為具體類型,紅皮書里講了一堆,in,out反正是跟類型轉換有關,那這里的in,out肯定是要作用于父類子類才有效果于是定義一個父類和子類,再實現接口大概就是這樣。

  實現時in out的類型可以隨意指定,只要滿足in是入參,out是返回類型就行了。既然能這樣寫,那就這樣吧。紅皮書里寫到用了in之后實現接口的類能怎么怎么轉換,用out之后能怎么怎么轉換,反正一大堆,很繞,重點就是實現后,父類子類分別作為泛型類型之間能倒騰了。管他的,我們自己來倒騰下吧。

 

-----------------------------------------------------------------關于轉換的驗證-------------------------------------------------------

 

  1.現在GT_ClassC 繼承自 GT_InterfaceC<GT_Child, GT_Parent>,于是這樣寫是OK的。

  既然是父類子類對泛型不同實現的轉換,那我們就修改左邊的父類或子類泛型,看看能不能轉換。首先把左邊的子類改為父類,于是是兩個父類實現的泛型接口。

  結果是不能自動轉換,為什么左邊的參數從子類變成父類,就報錯了。

  來捋一捋,首先左邊是in修飾,所以左邊的泛型肯定只能是入參類型。我們看看GT_ClassCShow方法的入參是什么,是子類GT_Child對吧,假設方法體里已經調用了子類的相關成員,這時候我們把入參類型改成他的父類,會有什么問題。子類的成員父類不一定有,這就是關鍵,方法體有異常風險。所以這里的入參類型的只能兼容GT_Child的同類或子類

  是不是呼之欲出,in修飾的類型在泛型接口實現后相互轉換時,左邊的該類型只能是右邊該類型的同類或子類。

  再看紅皮書里對in抗變的描述,大意父類到子類的轉換(這樣寫誰明白),其實意思應該是:in修飾的類型只能向下兼容,編譯器允許轉換右邊的類型被轉換成子類。

 

  2.那再來看看out吧,out是GT_InterfaceC第二個類型的修飾符,所以我們在剛才不報錯的寫法上修改下第二個參數,于是

  果然,報錯了。那就捋一捋吧。

  out關鍵字修飾的類型只能是返回類型,看看GT_ClassCShow方法的返回類型是什么,GT_Parent對吧。為什么把左邊的out類型從父類改為子類就報錯了呢。結合上面的理解,因為該類型肯定是返回類型,假設返回的是父類,現在要把父類轉換成子類會怎樣。看看實驗結果

  父類到子類的轉換不能隱式轉換,因為父類可以有多個子類,轉換需要強制轉換。所以Show方法的返回類型只能隱式轉換成父類。

  是不是呼之欲出:out修飾的類型在泛型接口實現后相互轉換時,左邊的該類型只能是右邊該類型的同類或父類。簡單來說out修飾的類型只能向上兼容。和in真的是前呼后應啊。再回頭一想,要是in和out不限制:in只能修飾入參類型,out只能修飾返回類型,是不是上面的理論都不能成立,現在知道為什么有這樣的約束了吧。

 

------------------------------------------------------------分析-----------------------------------------------------------------------------------

 

  下面貼上寫代碼的時候的一點總結幫助理解

  其實剛開始GT_ClassC實現接口的類型,in out對應的類型是和現在圖中顛倒的,這樣一來,應該會有人想到了,不管左邊是什么類型,右邊都能轉換成功。所以剛開始我把左邊子類父類不管怎么替換都沒問題,我以為只要用了in out約束就可以任意寫了,但是機智的我立馬覺得此事必有蹊蹺。要是沒問題,還講什么協變抗變,直接說入參in約束,返回類型out約束就行了。于是有了以上推論。

 

  看到這是不是覺得該完了。too young, simple is good.

  3.再回頭看最開始的疑問,為什么只能是泛型接口或泛型委托可以使用in out。我們寫兩行代碼看看

  定義一個簡單泛型,發現不管怎么轉換都不行。

  泛型轉換是基于接口,先不管具體實現類型是什么,兩個同一泛型接口的成員當然可以轉換。而具體的類型轉換就要基于in和out的約束了(沒有關鍵字約束不管具體實現類型之間有無繼承關系都無法轉換,已驗證)。

   再看看委托,委托可以理解成一種類型,定義(指定參數類型和返回類型的方法)的類型。是不是和泛型很像,事件就相當于委托的實現,是不是有泛型接口的影子。

  泛型委托涉及到泛型,有入參和返回類型當然可以使用in out 約束泛型達到(泛型委托實現互相轉換)的編譯器檢查。

 

  下面貼上比較完整的demo方便大家整體查看,好些報錯的驗證性代碼刪了,有興趣大家可以自己驗證一下。


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

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