通常我們設計一個.NET應用程式,無需考慮過多就可以讓我們的程式正確的跑起來。但是,當我們的設計日益複雜時,稍不小心就會因為不清楚.NET代碼的執行順序以及規則而使我們陷入困境。先來個小demo,檢查一下你對它瞭解多少吧。 1 public class MainTest
2 {
3 static void Main(string[] agrs)
4 {//斷點① (沒錯,是花括弧)
5 Console.WriteLine(Test1.Getddd());//斷點② (調用靜態方法)
6 Console.WriteLine(Test1.ddd);//斷點③ (調用靜態欄位)
7 }
8 };
9 public class Test1
10 {
11 public static string ddd = Test2.kkk;//斷點④
12 public static string Getddd()
13 {
14 return "ddd";//斷點⑤
15 }
16 };
17 public class Test2
18 {
19 public static string kkk = "kkk";//斷點⑥
20 };
問題一:上面的代碼中,我定義了六個斷點,你不妨思考一下,六個斷點處的代碼執行順序如何。下面有兩個觀點,你認同哪個呢?
觀點1 : ① -- ⑤ -- ② -- ⑥ -- ④ -- ③
觀點2 : ⑥ -- ④ -- ① -- ⑤ -- ② -- ③
(如果你還有其他觀點,歡迎跟帖討論)
如果你支援觀點1,那你就很值得把這篇文章一看了。如果你肯定觀點2是正確的,那說明你已經出師,無需再看下文講解。
的確,觀點2是正確。上面的代碼主要的關鍵點有:
1.函數中調用靜態欄位。
2.函數中調用靜態方法。
3.類的靜態欄位中調用另一個類的靜態欄位。
我們知道,類中的靜態欄位在整個程式中只會初始化一次,而靜態方法卻是每調用一次都要執行一次。
問題二:我們的僅有一次的類中的靜態欄位是何時被初始化的呢?
觀點1 : 應用程式載入時就先把所有的靜態欄位都初始化一次。
觀點2 : 當第一次調用到某個靜態欄位的語句時才去初始化那個靜態欄位,並且把這個類中的其他靜態欄位也初始化。
觀點3 : 當第一次調用到某個靜態欄位的語句時才去初始化那個靜態欄位,並且只初始化這一個靜態欄位。
又要做選擇了,你選擇哪個呢?答案是~~~~~~~~~~~都不對-_-!
正確的初始化時機,我的理解是:當某個類第一次被執行個體化或類的靜態方法第一次被調用或類的靜態欄位第一次準備要調用前,就嘗試去初始化這個類的所有靜態欄位。為了驗證這一點,我可以把上面的代碼簡單的修改一下: 1 public class MainTest
2 {
3 static void Main(string[] agrs)
4 {//斷點① (沒錯,是花括弧)
5 Console.WriteLine(Test1.Getddd());//斷點② (測試調用靜態方法時類Test1中執行的順序如何)
6 //Test1 t1 = new Test1();//斷點③ (測試初始化類時類Test1中執行的順序如何)
7 }
8 };
9 public class Test1
10 {
11 public static string ddd = "kkk";//斷點④
12 public static string Getddd()
13 {
14 return "ddd";//斷點⑤
15 }
16 };
我們可以看到,代碼執行的順序是:1 -- 4 -- 5 -- 2 。第一次調用了Test1.Getddd()方法時,就對靜態欄位ddd進行了初始化。我們可以修改代碼,在Main函數裡先執行個體化一個Test1,這時我們可以看到當執行個體化一個類的時候靜態欄位也被初始化了。
問題三:為什麼說上文說"類中的靜態欄位第一次準備要調用之前"?
"準備要調用之前"指的是一個類中(如第一段代碼中的Class Test1中執行初始化ddd時要調用到的Class Test2中的靜態欄位kkk)或是在一個函數體中(如第一段代碼中Main函數體中將要調用的Class Test1中的靜態欄位ddd)中將要調用到某個類的靜態欄位時,編譯器會首先檢查到這一情況,最先將該靜態欄位所屬的類中的所有的靜態欄位初始化,再開始執行類或函數中要執行的代碼。這就說明了為什麼第一段代碼中沒有先執行斷點①而是先去初始化了那些靜態欄位。
因此,總結如上,頭腦中應該對於一個類、一個靜態欄位、一個靜態方法、一個靜態屬性(執行順序和靜態方法幾乎一樣)、類中的建構函式等等的執行先後順序有了比較清晰的瞭解。因此我個人總結了如下(希望批評指正):
1.當一個類被執行個體化時,首先檢查類中的靜態欄位是否已經初始化,如果沒有初始化,將最先執行靜態欄位的初始化代碼。(如果靜態欄位的初始化代碼中又調用了另一個類中的靜態欄位,將最優先執行另一個類中的靜態欄位的初始化。)接著,將執行非靜態欄位的代碼,然後再執行類的建構函式。
2.當調用類的靜態方法或屬性時,也是首先檢查類中的靜態欄位是否已經初始化,如果沒有初始化,將最先執行靜態欄位的初始化代碼。接著再執行靜態方法或屬性內部的代碼。
3.某個函數體中存在使用了某個類的靜態欄位時,無論靜態欄位在函數體的什麼位置,只要這個靜態欄位沒有被初始化,將優先於執行這個函數之前,執行該靜態欄位的初始化(實際上將該類中的所有靜態欄位都初始化了)。
以上包含很多個人觀點,有不對的地方希望大家批評指正!