程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 讓我們用心感受泛型接口的協變和抗變out和in,outin

讓我們用心感受泛型接口的協變和抗變out和in,outin

編輯:C#入門知識

讓我們用心感受泛型接口的協變和抗變out和in,outin


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

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

只能用於接口和委托的泛型,於是這樣寫

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

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

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

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

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

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

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

  是不是呼之欲出,in修飾的類型在泛型接口實現後相互轉換時,左邊的該類型只能是右邊該類型的同類或子類。再看紅皮書裡對in抗變的描述,大意父類到子類的轉換(這樣寫誰明白),其實意思應該是:in修飾的類型只能向下兼容,編譯器允許轉換成子類。

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

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

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

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

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

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

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

 

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

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

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

泛型轉換是基於接口,先不管具體實現類型是什麼,兩個同一泛型接口的成員當然可以轉換。而具體的類型轉換就要基於in和out的約束了(左邊和右邊的類型不是繼承關系會報錯,已實驗)。

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

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

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

  1. 上一頁:
  2. 下一頁: