1.靜態直觀的特點 靜態成員最顯著的一個特點就是它的作用域是全局的。只要在調用處引入了對應的命名空間,那麼我們可以在代碼任何地方都可以直接使用。凡是具有全局特征的東西我們就可以考慮使用靜態。在實際的開發中,靜態欄位我們常用實現數據的共用,修飾為靜態的方法當做常用的工具方法來使用。 2.命名上的思考 ...
1.靜態直觀的特點
靜態成員最顯著的一個特點就是它的作用域是全局的。只要在調用處引入了對應的命名空間,那麼我們可以在代碼任何地方都可以直接使用。凡是具有全局特征的東西我們就可以考慮使用靜態。在實際的開發中,靜態欄位我們常用實現數據的共用,修飾為靜態的方法當做常用的工具方法來使用。
2.命名上的思考
靜態從名稱的對立面上可以想到一個詞叫動態,這裡所說的動態可以隱喻為實例成員。實例成員之所以可以形容為動態因為在記憶體中它的創建和釋放資源是根據實例化對象或GC(垃圾回收)不斷變化的。而靜態成員在記憶體中很少會發生變化,靜態成員的創建是在第一次訪問對應的類型時進行的創建,資源釋放是在應用程式結束的時候。
3.靜態和實例(非靜態)的對比
靜態 |
實例(非靜態) |
需要static關鍵字 |
不需要static關鍵字 |
無法實例化只能使用類名調用 |
必須實例化對象後才能調用 |
在靜態方法中,可以訪問靜態成員 |
在實例方法中,可以訪問靜態成員 |
在靜態方法中不可以直接訪問實例成員,如需訪問需要創建對象 |
在實例方法中,可以直接訪問實例成員 |
調用前初始化(在創建第一個實例或引用任何靜態成員之前) |
實例化(調用構造函數)對象時初始化 |
4.靜態成員的初始化的創建過程(本文重點)
當一個類當中包含了靜態成員時,程式內部是如何初始化靜態成員的呢?
4.1代碼示例說明1
如運行上圖的程式,對於這種情況實際上即使是在程式運行時該類中的靜態成員是沒有被創建(初始化的)。之所以是這種情況,因為程式中靜態成員對應的類型並沒有在任何位置出現過訪問該類型的代碼。而靜態成員的創建是在第一次訪問這個類型時創建的。
4.2代碼示例說明2
在遵循靜態成員的創建原則後,此圖對代碼進行改動後包含了對類型的訪問,那麼這個時候靜態成員是會被創建的。
從直觀的代碼上來看,很顯然會產生一個對靜態成員初始化的誤解,因為代碼中的靜態成員在聲明時已經進行初始化賦值的操作。其實這種方式只是一種語法糖,是為了方便程式員給的簡單語法。實際上對於類型內部的欄位賦值是通過構造函數來完成的。
我們現在思考一個問題:上圖代碼中並沒有編寫任何的構造函數,那麼靜態成員是如何進行初始化賦值的呢?
通過一般的調試時並看不出程式內部的執行過程,也很難找尋問題的答案。此處為了探尋上述的問題我可以通過反編譯工具查看下上圖中反編譯後的代碼。如圖:
上圖中我們可以看到通過反編譯後MyClass內部包含了一個靜態的構造函數並且為靜態成員進行賦值。此處可以得到一個結論:當在類中定義靜態成員的時候,實際上也定義了一個靜態構造函數。
根據反編譯代碼的情況我們將靜態構造函數顯示的寫在代碼中調試,來驗證下靜態構造函數是否被調用,然後觀察下實例成員和靜態成員初始化的順序。如圖:
根據上圖中的演示可以看出靜態構造函數被調用,並且會先調用靜態構造函數然後在調用實例構造函數。
5.關於靜態構造函數需要註意強調的幾點
- 靜態構造函數的定義上:不能有訪問修飾符並且必須無參,一個類或結構當中只能有一個構造函數,不允許重載和繼承。
- 靜態構造函數調用上:靜態構造函數在編寫代碼時不能像實例構造函數那樣直接調用,它的調用是由公共語言運行時(CLR)調用。並且靜態構造函數只會調用一次。
以下代碼運行圖驗證靜態構造函數只會調用一次的情況:
使用建議:
儘量少使用靜態成員,因為靜態成員的生命周期是在應用程式結束後釋放的,然而GC(垃圾回收)沒有對其進行記憶體的回收管理。促使會上時間的占用記憶體,並且會產生一些記憶體垃圾。
學習感想:
看似使用簡單的靜態特性,有人運用設計出了軟體模式的單例模式。淬煉堅實的基本功永遠都不算晚,個人覺得編程最難的是在運用,就算學會在高端的技術如果不會組合運用,也還是沒有達到真正掌握的程度。