第 80 講:C# 2 之分部類型
C# 2 誕生了一種新的語法機(jī)制,它讓我們的數(shù)據(jù)類型的書寫和使用都變得更為方便。
Part 1 分部類型的概念
考慮一種情況。如果我們?cè)跁鴮懘a的時(shí)候必須把代碼放在同一個(gè)類型里面,如果這個(gè)類型非常龐大,處理起來相當(dāng)麻煩的話,那么這個(gè)類型經(jīng)常就會(huì)超過幾千行甚至上萬行代碼。如果這樣的代碼列在一起,就非常不方便我們自己書寫代碼的時(shí)候去查找 API 的使用,以及 API 的執(zhí)行過程。因此,C# 2 里帶來了一種新的機(jī)制:分部類型(Partial Type)。
1-1 基本語法
我們可以允許一個(gè)類型聲明分為多個(gè)文件來存儲(chǔ),每一個(gè)文件的類型聲明都使用同一種數(shù)據(jù)類型的名字。比如說,假設(shè)我有一個(gè)數(shù)據(jù)類型叫做 StringHandler
,那么我可以這么去寫代碼:
我們定義其中一個(gè)文件為主類型文件(Main File),它存儲(chǔ)和標(biāo)識(shí)這個(gè)數(shù)據(jù)類型的修飾符,以及一些比較關(guān)鍵的成員。接著,我們可以將一些文件分開獨(dú)立存儲(chǔ),里面可以實(shí)現(xiàn)一些分開的內(nèi)容,比如嵌套類,等等。每一個(gè)需要分離成不同文件的類型的聲明上都帶上 partial
這個(gè)新的關(guān)鍵字,挨著類型修飾符 struct
出現(xiàn)。
這里所說的主類型文件只是邏輯存在的一種說法,假設(shè)我這個(gè)類型是 public sealed class
修飾的,那么你完全可以將 public
和 sealed
修飾到不同的分部類型的聲明上去:
編譯器將自動(dòng)合并所有的修飾符,即最終的修飾符是 public sealed class A
,編譯器知道去合并修飾符。不過,這種情況下我們就無法說清楚哪一個(gè)文件是主要的了。
不過,我們不建議你這么寫代碼。我們建議你在書寫和使用的時(shí)候,盡量把修飾符放在同一個(gè)文件里去,將其作為主類型文件。
1-2 一般用途
一般來說,這種用法一般實(shí)現(xiàn)和使用在嵌套類型里比較多一些。比如剛才的 StringHandler
類型,假設(shè)它用來拼接長字符串的話,它應(yīng)該就必須具有支持 foreach
循環(huán)的迭代器類型。假設(shè)這個(gè)迭代器類型是我們自己實(shí)現(xiàn)的:
Enumerator
StringHandler
類型,因此 partial
用在 StringHandler
上,而不是嵌套類型 Enumerator
上面。
Part 2 常見分部類型里需要注意的問題
2-1 partial
修飾符的位置
另外,目前只有 delegate
和 enum
無法使用分部類型機(jī)制,而 class
、struct
和 interface
都可以使用 partial
修飾符來指示類型可以多文件分開存儲(chǔ)。至于為什么委托和枚舉不行……因?yàn)槲新暶髦恍枰痪湓?;而枚舉類型里沒有任何需求去分離不同的委托字段信息。partial
關(guān)鍵字一定要放在類型修飾符 class
、struct
和 interface
的前面。目前來說,partial
關(guān)鍵字的位置只能在類型修飾符的左邊緊挨著,也就是說:
partial public sealed class A
public partial sealed class A
這些都是不對(duì)的寫法。因?yàn)槲恢貌徽_。
2-2 重復(fù)使用相同的修飾符
C# 允許我們?cè)诓煌奈募鲜褂孟嗤男揎椃?。也就是說,你多次在不同的分部類型文件上使用同一個(gè)修飾符,C# 是允許的。
2-3 分部類型的嵌套
C# 甚至支持分部類型嵌套。換句話說,你可以將嵌套類型分文件存儲(chǔ)。不過,當(dāng)前類型,以及它所處類型,以及所處類型的所處類型(可能還有更多的嵌套級(jí)別),全部標(biāo)記上 partial
即可:
A
、B
和 C
類型分文件存儲(chǔ)的時(shí)候都標(biāo)記 partial
請(qǐng)注意文件分離出來后的樣子和修飾符的使用情況。
2-4 分部類型的類型繼承和接口實(shí)現(xiàn)
是的,分部類型和普通的數(shù)據(jù)類型沒有什么區(qū)別,只是分離開了罷了。分部類型也能實(shí)現(xiàn)接口和從基類型派生的規(guī)則。
沒有什么區(qū)別。C# 也確實(shí)允許你隨意去完成類型繼承機(jī)制和接口實(shí)現(xiàn)機(jī)制,而且你甚至也能多次從同一個(gè)基類型派生,以及多次實(shí)現(xiàn)同一個(gè)接口。編譯器它知道你已經(jīng)完成了對(duì)接口的實(shí)現(xiàn),以及對(duì)基類型的派生過程,因此它不會(huì)強(qiáng)求你必須只寫一次的。
2-5 為什么編譯器會(huì)允許我們多次重復(fù)書寫相同的東西?
這個(gè),其實(shí)是因?yàn)榫幾g器生成代碼的時(shí)候方便靈活處理。編譯器有些時(shí)候?yàn)榱朔奖?,就?huì)直接把頭部全部讀取下來,然后抄一遍。C# 這樣的允許就保證了這種機(jī)制是可以允許和接受的;試想一下,假設(shè) C# 不允許這種機(jī)制,即要求修飾符只能出現(xiàn)一次、不得重復(fù)書寫,而且里面的接口實(shí)現(xiàn)、基類型派生的這些語句全部只能寫一次的話,是不是就有點(diǎn)不靈活?
比如說,我有一個(gè)類型:
A
2-6 partial
修飾過的類型仍可以不分部
為了語法的通用性和靈活性,C# 允許對(duì)不分部的數(shù)據(jù)類型使用 partial
關(guān)鍵字。也就是說,如果這個(gè)類型沒有分離到不同的文件的時(shí)候,這個(gè)類型仍然可以使用 partial
關(guān)鍵字。在這種情況下,partial
有沒有都沒關(guān)系,不影響編譯器執(zhí)行、分析和生成代碼。
不過,要注意一點(diǎn)的是,這樣修飾的話會(huì)帶來一些隱患,這一點(diǎn)我們稍后會(huì)給出說明。
Part 3 泛型類型的分部
3-1 泛型類型的分部規(guī)則
泛型類型比正常的類型聲明多了一個(gè)泛型參數(shù)部分,以及泛型參數(shù)約束部分(如果泛型參數(shù)需要約束的話)。C# 對(duì)泛型參數(shù)約束的要求還是和前面都差不多,但是!
因?yàn)榉盒蛥?shù)個(gè)數(shù)不同的同名類型是構(gòu)成類型重載性的,所以泛型參數(shù)必須要在分部為多文件的時(shí)候,都全部寫出來,不過,泛型約束可以不重復(fù)再寫一遍;寫一遍也行,但必須泛型約束是一樣的,不能篡改和變動(dòng)泛型約束。
比如下面給出的這個(gè) A<T>
類型。下面給出了所有它的分部類型。其中注釋掉的部分是錯(cuò)誤的寫法,原因也都寫在后面了。
如果看不懂英語的話,我再解釋一下吧。
partial class A<T>
:可以,允許不寫泛型約束;partial class A<T> where T : struct
:不可以,因?yàn)榉盒图s束不一樣;partial class A
:可以,但由于它和A<T>
類型構(gòu)成類型重載,所以這個(gè)A
完全就是一個(gè)新的數(shù)據(jù)類型;partial class A<T> where T : class
:可以;partial class A<T1>
:不可以,因?yàn)榉盒蛥?shù)名字不一樣,數(shù)據(jù)類型A<T>
和A<T1>
也不構(gòu)成重載;partial class A<T, U>
:可以,但它和A<T>
類型構(gòu)成類型重載,所以A<T, U>
也是一個(gè)全新的數(shù)據(jù)類型。
因此,請(qǐng)一定要注意問題。
3-2 為什么不建議在任何類型上都加上 partial
關(guān)鍵字?
另外,前文說到“留下隱患”指的就是這里的類型重載性。一個(gè)數(shù)據(jù)類型是不需要任何修飾符的,它將會(huì)默認(rèn)為 internal
修飾的類型。而你使用 partial
關(guān)鍵字但沒有注意類型的重載性的話,就完全可能使得這個(gè)類型完全和你原來的類型關(guān)聯(lián)不上,導(dǎo)致暗藏的程序問題和書寫代碼的問題,所以,我們并不建議用戶在自己實(shí)現(xiàn)類型的時(shí)候,隨時(shí)都加上 partial
關(guān)鍵字;相反,我們只建議你必須分離文件的時(shí)候才使用該修飾符。