国产精品天干天干,亚洲毛片在线,日韩gay小鲜肉啪啪18禁,女同Gay自慰喷水

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

第 69 講:C# 2 之泛型(三):泛型接口及泛型參數(shù)繼承

2021-12-03 22:08 作者:SunnieShine  | 我要投稿

前面的內(nèi)容告訴了大家如何使實(shí)現(xiàn)一個(gè)泛型類型(結(jié)構(gòu)和類)。今天我們來(lái)介紹一下泛型參數(shù)繼承的機(jī)制。

Part 1 創(chuàng)建一個(gè)泛型接口

先來(lái)簡(jiǎn)單的。我們來(lái)創(chuàng)建一個(gè)泛型接口類型。泛型接口類型就是一個(gè)接口類型帶有泛型參數(shù)??紤]一種情況。假設(shè)我想要提取一種集合類型,暫定名稱叫 ICollection<T>。這個(gè) T 表示這個(gè)集合類型存儲(chǔ)的每一個(gè)元素的類型,比如數(shù)組 T[]、列表 List<T> 的這個(gè) T。這個(gè)接口專門提取出一個(gè)集合應(yīng)該有的一些成員,以便我在以后使用的時(shí)候,要想實(shí)現(xiàn)一個(gè)集合,直接實(shí)現(xiàn)這個(gè)接口,通過(guò) IDE 提供的自動(dòng)實(shí)現(xiàn)接口成員的功能就可以完美產(chǎn)生一系列的成員,就不必自己手寫的時(shí)候忘記東西。那么這個(gè)接口大概長(zhǎng)這樣:

是的。和類型的聲明方式完全一樣,接口也不例外:接口如果想要帶有泛型參數(shù),在聲明的時(shí)候直接按照 <T> 的形式放在后面即可。然后我們就可以在里面追加一系列的成員用來(lái)表示一個(gè)集合應(yīng)該有的東西。一般正常思路下,它得是可 foreach 迭代的、可以使用索引器的、可以增刪查成員的。其中可迭代的對(duì)象我們要求它實(shí)現(xiàn) GetEnumerator 方法即可,而索引器,我們直接給出 get 方法即可,set 方法有些時(shí)候集合也不一定非得去實(shí)現(xiàn),所以不用給出;增刪查就不多說(shuō)了。來(lái)看下寫法:

大概這樣就可以了。注意我們這里用到了兩個(gè)新的數(shù)據(jù)類型:IEnumerable<T>Predicate<T> 類型。我們簡(jiǎn)單給大家說(shuō)一下這兩個(gè)類型是什么。

1-1 IEnumerable<T>IEnumerator<T> 接口

IEnumerable<T> 接口是 IEnumerable 接口的泛型版本。換句話說(shuō),原本的 IEnumerable 接口也帶有 GetEnumerable 方法,但由于非泛型,因此返回值是 object 類型來(lái)兼容所有數(shù)據(jù)的;現(xiàn)在我們有了 IEnumerable<T> 后,返回值類型就可以從 IEnumerator 改成 IEnumerator<T> 了。但是,它們都是包含完全一致的方法名:GetEnumerator。

IEnumerable<T> 的實(shí)現(xiàn)是這樣的:

原本的 IEnumerator 接口我們說(shuō)過(guò)了,它是用來(lái)啟動(dòng)迭代 foreach 循環(huán)的迭代器對(duì)象。它等價(jià)于這樣:

而現(xiàn)在我們有了泛型版本,因此這段代碼就相當(dāng)于改成了這樣:

這樣的話,在一定程度上避免了裝箱。而早期執(zhí)行語(yǔ)句下,代碼是這樣寫的:

現(xiàn)在有了泛型,我們就可以把代碼替換成這樣了:

所以,總的來(lái)說(shuō),這個(gè)接口的意義在于泛型化接口,以盡可能避免裝箱。

接著注意一下實(shí)現(xiàn)的問(wèn)題。由于 IEnumerable<T> 是繼承接口 IEnumerable 非泛型的接口類型。那么你在實(shí)現(xiàn)了 IEnumerable<T> 接口的時(shí)候,按照接口的繼承機(jī)制,你也必須同時(shí)也實(shí)現(xiàn) IEnumerable 接口的成員。

問(wèn)題是,這兩個(gè)接口都只有一個(gè)成員,而且它僅包含這一個(gè)成員,而且好巧不巧,它們都是叫 GetEnumerator,只有返回值不同。正是因?yàn)檫@個(gè)原因,在聲明 IEnumerable<T> 接口的時(shí)候,它里面帶的泛型版本的 GetEnumerator 方法的開(kāi)頭帶有一個(gè)修飾符 new 就是為了隱藏底層的成員。但隱藏歸隱藏,你實(shí)現(xiàn)了 IEnumerable<T> 接口也必須同時(shí)實(shí)現(xiàn)這兩個(gè)不同的方法。new 只是表示我這個(gè)方法不是從基類型繼承下來(lái)的東西,而只是巧合的時(shí)候取名遇到重名現(xiàn)象,為了規(guī)避繼承機(jī)制才需要追加 new 關(guān)鍵字的。但你既然兩個(gè)方法都得實(shí)現(xiàn),那么我必須得考慮一個(gè)問(wèn)題:實(shí)現(xiàn)都使用隱式接口實(shí)現(xiàn)就會(huì)導(dǎo)致重名而出現(xiàn)無(wú)法編譯的沖突。于是……

于是怎么樣呢?稍后我們解釋。

1-2 Predicate<T> 委托

是的。C# 靈活就靈活在,所有數(shù)據(jù)類別均可使用泛型。委托也不例外。這 Predicate<T> 委托想要代表什么方法呢?一個(gè)條件,一個(gè)以 T 類型作為判斷成員的條件。它的聲明是這樣的:

是的,傳入一個(gè) element,是 T 類型的。然后進(jìn)行運(yùn)算后,得到一個(gè) bool 結(jié)果。它一般用在哪里呢?比如這樣的場(chǎng)景:

是的。Predicate<T> 泛型委托用來(lái)獲取滿足條件的成員。這個(gè) predicate 變量假設(shè)就是 Predicate<T> 類型的話,那么它就可以通過(guò)接口自帶的 Invoke 方法調(diào)用里面的回調(diào)函數(shù),去判斷是否條件成立。

1-3 整個(gè)接口里的成員

那么這么一來(lái),接口里的東西就很容易看懂了。

  • 索引器:獲取第 index 號(hào)索引上存儲(chǔ)的元素;

  • Add 方法:添加一個(gè)元素到集合里;

  • Remove 方法:從集合里刪除一個(gè)指定數(shù)值的元素;

  • Find 方法,帶 T 參數(shù):找到集合里數(shù)值為 element 的元素,返回它的索引;

  • Find 方法,帶 Predicate<T> 參數(shù):找到集合里滿足指定條件的元素,返回它的索引。

Part 2 從這個(gè)接口派生

2-1 泛型參數(shù)繼承

很棒。接口有了,下面我們假設(shè)實(shí)現(xiàn)了一個(gè)自己寫的類型,然后想要從這個(gè)接口派生。

注意這里的語(yǔ)法。在 SequenceList<T> 類型后帶有泛型參數(shù) T。而我們從一個(gè)泛型接口派生。我們定義 ICollection<T> 接口的 T 表示的是集合的元素,而 SequenceList<T>T 難道就不是了嗎?那很顯然是的鴨。既然這個(gè) SequenceList<T>T 就是每一個(gè)元素的類型的話,那么按照接口實(shí)現(xiàn)的基本規(guī)則,我很明顯是想要讓這個(gè) T 作為泛型接口的實(shí)現(xiàn)參數(shù)才是。

可問(wèn)題是,本來(lái) T 就是泛型參數(shù)了,它自己都不確定,難道還能拿來(lái)替換別的類型里的泛型參數(shù)?是的。這就是 C# 一個(gè)新的機(jī)制:泛型參數(shù)繼承(Inheritance of Type Argument)。你仔細(xì)看看就會(huì)發(fā)現(xiàn),我 Predicate<T> 不也這么使用了嗎?

C# 認(rèn)為,你這個(gè) T 自己雖然是泛型參數(shù),但是在類型的聲明和它的大括號(hào)這段代碼里,這個(gè) T 就會(huì)自然而然地被認(rèn)為是一個(gè)普通的數(shù)據(jù)類型,只是我們不知道是什么具體的類型。這個(gè)時(shí)候我們嘗試讓 T 作為一個(gè)類型來(lái)使用,因此才能有之前的 default(T) 這些語(yǔ)法。而現(xiàn)在,在類型聲明里面,即使我們使用到了 T 作為比如 int Find(Predicate<T> predicate) 的一部分,但仍然 T 是按實(shí)際類型在看待,所以這個(gè)地方的 T 編譯器是不會(huì)管你的;而正相反地,這個(gè)寫法反而是我們以后自己實(shí)現(xiàn)泛型數(shù)據(jù)類型里,常見(jiàn)的寫法。

稍微注意一下用詞。這里的術(shù)語(yǔ)叫泛型參數(shù)繼承,用的是“繼承”,但它仍然和普通的類型繼承有所不同。泛型參數(shù)的繼承則是基于普通類型在實(shí)現(xiàn)接口或類的繼承機(jī)制的,而它帶有的泛型參數(shù)可以傳入到泛型基類或泛型基接口里當(dāng)實(shí)際泛型參數(shù)。

2-2 泛型接口的顯隱式接口實(shí)現(xiàn)

很好。下面我們來(lái)解釋一下 Part 1 里沒(méi)有解釋的問(wèn)題:由于 IEnumerable<T>IEnumerable 接口里的同名但不構(gòu)成重載的方法 GetEnumerator 無(wú)法直接實(shí)現(xiàn),因?yàn)槊Q會(huì)沖突,那么怎么辦呢?

我們?nèi)匀皇褂?SequenceList<> 類型來(lái)描述和演示這個(gè)問(wèn)題的解決辦法。由于沖突是無(wú)法避免的,因此我們需要使用緊急措施:顯式接口實(shí)現(xiàn):因?yàn)轱@式接口實(shí)現(xiàn)可以規(guī)避重名方法的現(xiàn)象。我們選取一個(gè)不常用的接口,它實(shí)現(xiàn)的內(nèi)容以顯式接口實(shí)現(xiàn)的形式呈現(xiàn)出來(lái),避免沖突。那么哪個(gè)不常用呢?顯然是非泛型版本 IEnumerable 接口了。因?yàn)榉盒蜋C(jī)制在絕大多數(shù)情況下都比非泛型機(jī)制要更好,所以我們基本上可以完全放棄掉非泛型的情況,而且也能達(dá)到一致的運(yùn)行目的和效果。因此,我們使用顯式接口實(shí)現(xiàn)隱藏掉 IEnumerable 接口的成員:

我們僅需隱藏其中一個(gè)即可。當(dāng)然,兩個(gè)你都可以使用顯式接口實(shí)現(xiàn),不過(guò)一般我們?cè)谌魏握G闆r下都建議使用隱式接口實(shí)現(xiàn),因此不必顯化實(shí)現(xiàn)。

2-3 泛型接口的多態(tài)

由于你實(shí)現(xiàn)了這樣的泛型接口,因此按照多態(tài)性的規(guī)則,當(dāng)前類型是可以隱式轉(zhuǎn)換為接口類型的實(shí)例的;反之,接口類型的實(shí)例可以強(qiáng)制轉(zhuǎn)換為當(dāng)前類型的實(shí)例(如果類型匹配的話)。

反之,因?yàn)榻涌陬愋筒恢缿?yīng)該往什么類型上轉(zhuǎn)化,并且轉(zhuǎn)換可能失敗,因此需要強(qiáng)制轉(zhuǎn)換:

比如這樣。

Part 3 泛型接口的多角色實(shí)現(xiàn)

泛型類型的誕生使得接口有一種新的“黑科技”:多角色接口實(shí)現(xiàn)。

3-1 基本實(shí)現(xiàn)和“多角色”的多態(tài)性

思考一個(gè)問(wèn)題。假設(shè)我有一個(gè)集合,但這個(gè)集合擁有多種迭代行為。比如 StudentCollection 集合類型存儲(chǔ)的是一系列學(xué)生的信息,那么我們迭代可以直接迭代每一個(gè)學(xué)生的信息;但有些時(shí)候也不一定非得是迭代 Student 的實(shí)例,比如我還可以只迭代學(xué)生的 ID(string 類型)之類的。

我們理想的代碼是這樣的:

可問(wèn)題在于,方法的重載是不允許只有返回值不同的。換句話說(shuō),兩個(gè)方法如果只有返回值不同的話,兩個(gè)方法仍然不作為重載成員出現(xiàn)。因此,這樣的代碼只能存在于理論里。

別忘了?,F(xiàn)在我有了泛型接口了,我們就可以這么做了:我們同時(shí)實(shí)現(xiàn)兩次 IEnumerable<T> 接口,只是泛型參數(shù)一個(gè)是 Student,而另外一個(gè)則是 string,然后改一下實(shí)現(xiàn)。

還記得接口的顯式實(shí)現(xiàn)嗎?接口是可以顯式實(shí)現(xiàn)的,這就是為了避免重名成員導(dǎo)致無(wú)法繼承的問(wèn)題。而我們可以利用顯示接口實(shí)現(xiàn)的機(jī)制來(lái)達(dá)到我們以前做不到的操作。

因?yàn)榻涌?IEnumerable<T>IEnumerable 接口派生,因此一定仍需要實(shí)現(xiàn)接口 IEnumerable 的成員。因此最后第 9 行的代碼仍舊不可少。

請(qǐng)注意語(yǔ)法。C# 甚至允許我們同時(shí)實(shí)現(xiàn)完全相同的接口類型多次,只要泛型參數(shù)的實(shí)際類型不同就可以。這種實(shí)現(xiàn)機(jī)制稱為多角色實(shí)現(xiàn)(Multi-role Implementation)。

有了這樣的機(jī)制后,我們就可以使用 foreach 語(yǔ)句同時(shí)實(shí)現(xiàn)兩個(gè)不同的方法:

兩種寫法全部都可以了。而且它們的不同迭代對(duì)象也會(huì)自動(dòng)“路由”到對(duì)應(yīng)的 GetEnumerator 方法上,你甚至無(wú)需擔(dān)心實(shí)現(xiàn)機(jī)制沖突的問(wèn)題——C# 可以幫你區(qū)分開(kāi)不同的調(diào)用。

另外,多角色接口實(shí)現(xiàn)也不影響它自身和接口類型之間的強(qiáng)制轉(zhuǎn)換和隱式轉(zhuǎn)換。這種東西的多態(tài)和正常的多態(tài)是一樣的,只不過(guò)多角色實(shí)現(xiàn)大不了就多幾個(gè)轉(zhuǎn)換的可能罷了:

這些轉(zhuǎn)換也都是可以的。

3-2 避免多角色接口實(shí)現(xiàn)

多角色接口實(shí)現(xiàn)是一種黑科技,正是因?yàn)?C# 允許顯式接口實(shí)現(xiàn),也允許我們實(shí)現(xiàn)多次同一個(gè)接口(只要泛型參數(shù)的實(shí)際類型不同),因此才會(huì)有這樣的情況出現(xiàn)。

可問(wèn)題在于,這樣的實(shí)現(xiàn)是有問(wèn)題的。思考一下面向?qū)ο罄锏慕涌谑侨绾我环N關(guān)系:接口實(shí)現(xiàn)等于類型能做什么。類型實(shí)現(xiàn)了 IEnumerable 接口說(shuō)明類型可以迭代,類型實(shí)現(xiàn)了 IComparable 接口說(shuō)明類型可以比較大小,類型實(shí)現(xiàn)了 IEquatable 接口說(shuō)明類型的實(shí)例可以判斷是否包含相同的內(nèi)容,而類型實(shí)現(xiàn)了 ICollection<T> 接口說(shuō)明類型可以做集合可以做的事情。諸如此類。

可問(wèn)題是,我們讓一個(gè)所謂的 StudentCollection 類型實(shí)現(xiàn)了 IEnumerable<string>,它的目的是什么呢?是不是應(yīng)該表示 StudentCollection 的實(shí)例可以按字符串類型進(jìn)行成員迭代???可是,按正常思考一般也都不會(huì)想到,StudentCollection 的迭代過(guò)程怎么會(huì)和一個(gè)字符串綁定關(guān)聯(lián)起來(lái)。因此,這樣的接口實(shí)現(xiàn)是不合面向?qū)ο蟮幕炯s定的。這個(gè)是這么實(shí)現(xiàn)接口的第一個(gè)問(wèn)題。

第二個(gè)問(wèn)題是,在稍后我們會(huì)對(duì)泛型展開(kāi)協(xié)變和逆變性質(zhì)的討論,在此我們將會(huì)討論為什么一個(gè)泛型類型是不變的,以及類型怎么轉(zhuǎn)換成協(xié)變和逆變的對(duì)應(yīng)類型,以及泛型委托類型的協(xié)變性。如果我們使用了多角色的接口實(shí)現(xiàn),會(huì)破壞安全的類型轉(zhuǎn)換,使得混淆泛型參數(shù)的協(xié)變和逆變過(guò)程。這個(gè)點(diǎn)稍微有點(diǎn)超綱,不過(guò)你先記住就行。

所以,這樣兩個(gè)原因可以說(shuō)明,我們都不建議你使用這種接口實(shí)現(xiàn)黑科技來(lái)完善你的代碼,只是存在這種機(jī)制是出于代碼的兼容性等考慮。

Part 4 泛型類型嵌套

C# 靈活的地方在于,你甚至可以嵌套使用泛型的數(shù)據(jù)類型。

如代碼所示,泛型類型仍可使用嵌套類型。不過(guò)稍微要注意一點(diǎn),就是泛型參數(shù)的問(wèn)題。

泛型參數(shù)是在這個(gè)數(shù)據(jù)類型里的任何一處地方都可以使用的,這意味著這個(gè) T 不管你里面有沒(méi)有嵌套別的類型,嵌套的什么類型,這個(gè) T 也都可以使用。那么,這樣的話就需要注意嵌套類型的泛型參數(shù)不要和外層數(shù)據(jù)類型的泛型參數(shù)重名。

如果重名,編譯器會(huì)生成一個(gè)警告信息,告訴你,我外層的數(shù)據(jù)類型已經(jīng)包含此泛型參數(shù)。如果你是無(wú)意重名的,請(qǐng)修改內(nèi)層嵌套的數(shù)據(jù)類型的泛型參數(shù)名稱,使之不要重名,否則,泛型參數(shù)重名后,嵌套數(shù)據(jù)類型的泛型參數(shù)會(huì)覆蓋掉同名泛型參數(shù),導(dǎo)致在嵌套類型里無(wú)法再看到和使用到外層的泛型參數(shù),說(shuō)白了就是隱藏掉了。

因此,我們不應(yīng)書(shū)寫代碼的時(shí)候出現(xiàn)這樣的情況,因此要么避免使用嵌套類型,要么避免使用重名的泛型參數(shù),特別是處于包含關(guān)系的情況下。


第 69 講:C# 2 之泛型(三):泛型接口及泛型參數(shù)繼承的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
五莲县| 龙南县| 烟台市| 三河市| 蒲江县| 稻城县| 贺州市| 汨罗市| 卫辉市| 新沂市| 扎兰屯市| 腾冲县| 安化县| 灯塔市| 龙南县| 乌鲁木齐县| 卢湾区| 沙洋县| 牡丹江市| 枣庄市| 怀化市| 文成县| 东乡| 广元市| 凌海市| 丁青县| 五莲县| 灵川县| 辽阳市| 集安市| 通海县| 辽宁省| 尼木县| 逊克县| 简阳市| 广宁县| 徐水县| 崇阳县| 台北市| 利辛县| 宁明县|