第 81 講:C# 2 之靜態(tài)類
有些時候,我們可能需要規(guī)劃出一個單獨的類型來存儲一些靜態(tài)成員。為什么這么做?舉個例子,我們可能會列舉一個數(shù)學操作類型,里面裝下了一系列的函數(shù)專門用于表示和計算數(shù)學操作有關的東西,比如正弦函數(shù)、余弦函數(shù)、向下取整等等。
如果我們零散放置它們,顯然不方便整理,于是 C# 2 為這種“工具類”單獨定義了一種語法機制:靜態(tài)類(Static Class)。
Part 1 類也可以修飾 static
了
在之前早期我們就提及過一個概念叫做工具類型。工具類型里存儲的就是這類成員,它們只用來計算和執(zhí)行固定功能的操作,它不用于實例化,因此這樣的類型也經(jīng)常被 sealed
修飾過,而構造器也經(jīng)常被 private
修飾符修飾,防止外部實例化,即這樣:
但經(jīng)常這么書寫代碼也不方便,因為你每次都得自己創(chuàng)建私有的無參構造器,挺麻煩的。
這里說的無參構造器指的是實例構造器,而不是靜態(tài)構造器。都知道靜態(tài)構造器的語法其實就在構造器前面加一個
static
修飾符,并沒有參數(shù),還不能加別的訪問修飾符。而這個構造器不是我們這里提及的內容。我們也沒有打算說這個,因為它不是我們這里要對比的內容,所以這里我們說的無參構造器指的是無參實例構造器。
C# 2 允許靜態(tài)類的機制。它的功能就是為了解決這種情況。我們只需要將類型修飾符從 sealed
改成 static
,然后刪除私有無參構造器即可:
是的,類型直接用 static
修飾了。下面我們來說說這種類型的相關細節(jié),以及解決的問題。
Part 2 靜態(tài)類的細節(jié)
靜態(tài)類的功能就是解決和簡化工具類型的語法的。下面我們來說說它們的實現(xiàn)細節(jié)。
2-1 只能修飾到類上
靜態(tài)類靜態(tài)類,顧名思義,就是靜態(tài)的類。是的,它只能用于類類型上,而結構、接口、委托或者枚舉,都是不能帶有 static
修飾符的。原因很簡單:接口、委托和枚舉類型沒有無參構造器的概念,而結構類型的無參構造器是永遠存在的,因此只有類類型的無參構造器可以自己定義,因此,靜態(tài)類只針對于類類型使用。
仔細思考一下,確實是這樣的,對吧?工具類型也不會用別的類型來表達,也沒人考慮會用接口來做吧?要知道接口里是不能帶有方法執(zhí)行邏輯的;而委托和枚舉就更不可能了。
2-2 靜態(tài)類里沒有無參構造器的概念
靜態(tài)類的語法里,我們只用了一個修飾符來完成;而私有構造器直接被刪除了。問題在于,這個構造器去哪里了?
答案可能讓你大吃一驚。C# 做了一個大膽的決定:靜態(tài)類不允許你自定義無參構造器,也不自動產(chǎn)生默認的無參構造器,說白了,靜態(tài)類里沒有無參構造器這個概念。是的,靜態(tài)類你無法書寫無參構造器的語法,也不允許你創(chuàng)建無參構造器,而在底層,C# 的靜態(tài)類里也不包含任何的構造器。是的,這種類型不產(chǎn)生默認的無參構造器,也不會允許你創(chuàng)建無參構造器。原因很簡單,因為靜態(tài)類的誕生就是防止你去實例化的,而它的目的已經(jīng)達到了:編譯器自身就可以限制你使用構造器語法來實例化一個靜態(tài)類的實例,所以,在底層的代碼里,靜態(tài)類哪怕真的沒有無參構造器也不會引起任何的運行時的嚴重錯誤。
2-3 const
修飾符
C# 早就有 const
修飾符,它表示這個數(shù)據(jù)自身在編譯之前就固定產(chǎn)生下來,并且運行時不可變動,只允許讀取的特殊量。它用來防止用戶在后期篡改變更對象的具體內容和數(shù)值。不過,因為 const
設計的復雜性和設計原則,const
修飾符只能修飾在基本數(shù)據(jù)類型和枚舉類型里,別的任何自定義類型全部不能使用這個修飾符。
const
作為編譯之前就固定下來的“常量”信息,它一定是靜態(tài)存在的,因此 const
修飾的對象從不使用 static
修飾符修飾,因為它的存在就是自帶靜態(tài)的概念的。因此,使用 const
修飾的字段可以存在于靜態(tài)類里。
static readonly
組合修飾符混淆起來,搞得完全分不清。
static readonly
可以是任何的表達式計算結果,它的存在是運行時只讀,即側重的是運行時不可修改;而 const
則指的是編譯器在編譯代碼期間就已經(jīng)固定和計算出結果了,它更側重的是編譯時常量的概念。從另一個角度來說,static readonly
的范圍更廣泛,因為它只是防止用戶去篡改它自身的數(shù)值;但 const
性能要更好一些。
2-4 靜態(tài)類是隱式密封的
這也是一句廢話。靜態(tài)類只是拿來給我們提供工具操作的內容,所以它自身是不含有任何的派生和繼承機制的。所以,一旦你修飾了 static
到類上去,那么這個類型就無法派生出新的類型,因為,派生和繼承機制是跟實例有關的概念。
2-5 靜態(tài)類只能包含靜態(tài)成員
這顯然也是一個廢話。不過,與其說它是廢話,不如認為這是從工具類型里抽取出來的一條基本規(guī)定。一個工具類型本來就只能用來處理和操作一些功能,而我總不能每次還得單獨實例化一下才去處理吧。所以,工具類型里肯定是不存儲實例成員的。那,“不存儲實例成員”不就是說“只包含靜態(tài)成員”嗎?
所以,靜態(tài)類的目的就是為了規(guī)范化工具類型的規(guī)則,所以它肯定也只能存儲靜態(tài)的成員。當然,工具類型里可以包含實例成員,這不是編譯器限制的,而是我們自身實現(xiàn)和設計數(shù)據(jù)類型的約定和約定俗成的規(guī)范;而靜態(tài)類不含有實例成員,是編譯器限制。相反,你往里加上實例成員肯定會受到編譯器錯誤信息。
這也是為什么類用的是 static
這個單詞來作為這個語義限制的原因——只有靜態(tài)成員的類型可不就是靜態(tài)類型嗎?
Part 3 盡量將 Program
類靜態(tài)化
C# 一直有一個比較晦澀難懂的概念:Program
類型。Program
類型不是一個固定的名字,這里想指的是給 Main
方法提供包裹的一個沒有啥特殊含義的類型;而之所以叫它 Program
類型,是因為系統(tǒng)最初生成的項目模板代碼里,包裹 Main
方法就用的是 Program
這個單詞。所以我們也經(jīng)常把 Program
這個詞和“包裹 Main
方法的類型”這個概念綁定起來。這個類型一般不用來實例化,因為它是受到 CLR 系統(tǒng)自身調用,所以跟它是否能夠實例化無關,因為程序第一個執(zhí)行的方法就是 Main
方法,而它自身就是靜態(tài)、私有的,而照樣能被調用。
正是因為這個原因,包裹封裝 Main
方法的類型沒有任何理由去實例化或者做別的操作和行為;相反,你真的要讓這個類型發(fā)展出別的操作的話,我建議你再單獨創(chuàng)建別的類型來完成這些任務,然后一直保持 Program
類型只含 Main
方法一個成員聲明。那么,既然它一般不拿來實例化,也不用于繼承和派生,更不放別的成員,那么我們最安全和最合適的方式是,給 Program
類型加上 static
修飾符。是的,把它搞成靜態(tài)類。
Part 4 總結
好久沒有總結了。下面我們來總結一下,C# 目前對類的修飾符的所有可能情況:
訪問修飾符:
public
、internal
、protected
、private
、protected internal
;繼承修飾符:
sealed
、abstract
;靜態(tài)修飾符:
static
。
是的,這次語法拓展,我們又多了一個修飾符 static
。