上篇 是基本語法基礎下的執行順序,包括繼承這個維度下的執行順序,我們可以依照的規律順下來,下面我們看下一些摸不到頭腦的情況
我們實驗 一個 類中的方法 去調用另一個非繼承類的情況, 我們主要看下 靜態構造函數 和沒有靜態構造函數執行順序上的差別
class Program { static void Main(string[] args) { //執行順序5 var de = new People("2"); //執行順序6 People.Instance.OutDemo(); //執行順序8 Console.ReadKey(); //執行順序10 } } public class People { public void OutDemo() { //執行順序9 Console.WriteLine($"HaHa"); } public static readonly People Instance = new People(); //執行順序1 public static Action Inssstance = () => { Console.WriteLine("11"); }; //執行順序4 private People() { //執行順序2 Console.WriteLine(3); //執行順序3 } public People(string ss) { //執行順序7 } }
執行順序 大致是上面十個步驟, 是不是感覺有點蒙蔽了,首先剛開始就沒進Main函數,跳到 People下面了,好吧,我們暫且給自己一個說服自己的理由,如果方法中有直接調另一個類的靜態成員的,先去走到另一個類中去初始化去.
ok, 接下來 執行順序1 中賦值的時候 調的 new People(),然后到 執行順序2 里面, 而另一個靜態的 執行順序4 在執行順序3走完 隨后 ,這個也可以理解,誰讓執行順序2 人家 賦值的時候 調的構造函數呢,然后執行順序4走完 ok,沒有靜態的了,調回到原 Program里的main方法了,這個和上篇講的靜態走完再走非靜態的不一樣,這個也可以理解,誰讓兩個類不是繼承的呢,到Program的Main后 繼續 5 、6、7、 8、9、10,這個順序沒有異議。
ok,我們似乎給了自己一個完美的解釋。先別高興的太早,接下來,我們一步步走起,我們再做一件事情:
我們給program定義一個靜態構造函數。然后再看下順序會是什么樣的:
class Program { static Program() { //然后執行 } static void Main(string[] args) { var de = new People("2"); People.Instance.OutDemo(); Console.ReadKey(); //執行順序10 } public static string sdds = "sdds"; //最先執行 }
我們加了個靜態字段,又加了個靜態構造函數, 整個順序: 最先執行 ,然后執行 ,執行順序1,執行順序2,......
沒有問題,和上片的知識體系相符,沒啥驚喜
既然Program下沒什么東東,我們就整下 People類吧,我們也給People類加個靜態構造函數,現在看看:
class Program { static Program() { //然后執行 } static void Main(string[] args) { 執行順序1 var de = new People("2"); 執行順序2 People.Instance.OutDemo(); //執行順序8 Console.ReadKey(); } public static string sdds = "sdds"; //最先執行 } public class People { static People() { //執行順序7 } public void OutDemo() { //執行順序9 Console.WriteLine($"HaHa"); } public static readonly People Instance = new People(); //執行順序3 public static Action Inssstance = () => { Console.WriteLine("11"); }; //執行順序6 private People() { //執行順序4 Console.WriteLine(3); //執行順序5 } public People(string ss) { //執行順序7 } }
眼尖的你發現 給People加了個靜態構造函數,現在順序變成了: 最先執行,然后執行,執行順序1,執行順序2,執行順序3,....,發現 Program靜態走完后,走進去Main方法了,不是先進People了。
啊,匪夷所思,那么沒關系,我們自解惑的思想繼續改造。我們在先前的一個思想上再加個情況,如果一個類A中去調用另一個類B的靜態成員,如果類B有定義了靜態的構造函數,那么我們就按順序走,不會先預先跑到B中初始化B的靜態相關的。只到代碼碰到B的地方,再去B中走靜態相關的部分。
ok,好像我們的理論又成熟了一步,我們在看看,還有什么意外不
又來了一個新點子去驗證下,如果我們在People中,在初始化靜態成員的時候,再去調第三個類的靜態成員呢:
..... public static readonly People Instance = new People(); //執行順序3 public static Action Inssstance = () => { Console.WriteLine("11"); }; //執行順序7 private People() { //執行順序5 var d= Money.Count //執行順序6 Console.WriteLine(3); } ........ public class Money { public static int Count= "4"; //執行順序4 } ......
我們發現相關順序仔細捋下來好像沒有什么意外, 執行順序 3 后 會去調 new People 調這個方法的時候,我們先有個預判斷,發現這個方法里面有個調用第三個類Money的靜態成員,而第Money又沒有定義靜態構造函數,嗯,符合跳轉的條件 ,然后 到了 執行順序4 , 然后是執行順序5 ,然后是執行順序6 ,執行順序7
接下來,迫不及待的給Money加個靜態構造函數
..... static People() { //執行順序10 } public static readonly People Instance = new People(); //執行順序3 public static Action Inssstance = () => { Console.WriteLine("11"); }; //執行順序9 private People() { //執行順序4 var d= Money.Count //執行順序5 Console.WriteLine(3); //執行順序8 } ........ public class Money { static Money() { //執行順序7 } public static int Count= "4"; //執行順序6 } ......
依然符合。結論依然牢固
還要繼續玩嗎,繼續新的懷疑
如果我們把不被別的地方調的靜態成員變成私有的呢,會不會影響順序了,這個我這里就不列了,答案是:不會
繼續,總覺的還有沒有挖掘的東西需要一探究竟
我們突然發現,上面我們調的都是另一個類的靜態字段,如果是調的另一個類的靜態方法呢,會不會也是這個規律呢
改造起來
class Program { static Program() { //然后執行 } static void Main(string[] args) { 執行順序1 // var de = new People("2"); //People.Instance.OutDemo(); People.xixi(); //執行順序2 Console.ReadKey(); //執行順序8 } public static string sdds = "sdds"; //最先執行 } public class People { public static string xixi() { //執行順序7 return "xixi"; } public void OutDemo() { Console.WriteLine($"HaHa"); } public static readonly People Instance = new People(); //執行順序3 public static Action Inssstance = () => { Console.WriteLine("11"); }; //執行順序6 private People() { //執行順序4 Console.WriteLine(3); //執行順序5 } public People(string ss) { } }
簡單測試發現,好像直接調另一個類的靜態方法沒有符合上面的規律,而是符合常規的規律,不過People加不加靜態構造函數,都不會有我們發現的”預先跳轉“,它和調用另一個類的靜態字段不一樣,就調用另一類的靜態字段有這個特殊性預先跳轉,具體大家可以測試下
如果我們在調用的時候再加上調用People那個靜態字段,那效應就會出來了:
....... static void Main(string[] args) { People.xixi(); People.Instance.OutDemo(); //有這個 就會先判斷People有沒有靜態構造函數,來決定是否在進main方法前先 預先跳轉 Console.ReadKey(); } ......
如果對調用層做個包裝呢
.... static void Main(string[] args) { //執行順序1 dtas(); //執行順序2 // Console.WriteLine(People.Instance.OutDemo()); Console.ReadKey(); } public static void dtas() { //執行順序4 People.Instance.OutDemo(); } ....... //假設People沒有靜態構造函數 ...... //public static readonly People Instance = new People(); //執行順序3 ....
結果也符合,進main方法了,在進dtas()方法時進行了預先判斷,注意,方法的包一層,我注釋的:Console.WriteLine(People.Instance.OutDemo());其實和 datas()是一個效果,都是包了一層,不要反應不過來喲
然后,我們對People的靜態字段再改下
...... static void Main(string[] args) { 執行順序2 People.Instance().OutDemo(); 執行順序3 Console.ReadKey(); //執行順序7 } ....... //假設People沒有靜態構造函數 ...... //public static readonly People Instance = new People(); //換成下面這中 public static readonly Func Instance = () => { 執行順序1 return new People(); 執行順序4 }; public void OutDemo() { 執行順序5 Console.WriteLine($"HaHa"); 執行順序6 } .......
我們發現這個調用靜態字段如果用委托的方式,會到 Instance這個沒錯,但是 字段的賦值部分(return new People())就在后期真正調的時候執行了,這個大家注意下這點
在最后給大家一個層次多點的例子,或許你們會吐槽哪有這么寫代碼的,現實情況下也不這么調啊,一點都不實際,其實還真有場景有可能這么調的,這只是一個映射現實代碼的一個 簡單的demo原型而已。你們能捋通下面的了嗎?再試着給Middle加個靜態構造函數呢?你們可以試試,我就不展示了
class Class1 { static void Main(string[] args) { //執行順序 6 var obj = People.Instance; //執行順序 7 Console.Read(); //執行順序 8 } } public class People { public static readonly ZIS2 Instance = Middle.mid; //執行順序 4 public static string dsds = "sdd"; //執行順序 5 } public class Middle { public static ZIS2 mid = ZIS2.Instance; //執行順序 3 } public class ZIS2 { public static ZIS2 Instance = new ZIS2(); //執行順序 1 private ZIS2() { //執行順序 2 } }
到現在為止,我們就暫且告一段落了,當然最后聲明下,上面的這些測試都是一次調用的情況,我們想大家應該知道。其實,上面說了那么一大堆,這個現象的原理是什么,編譯器編譯后他們都有什么不同呢