C# 靜態構造函數,靜態變量執行順序(升華版)


上篇 是基本語法基礎下的執行順序,包括繼承這個維度下的執行順序,我們可以依照的規律順下來,下面我們看下一些摸不到頭腦的情況

我們實驗 一個 類中的方法 去調用另一個非繼承類的情況,  我們主要看下  靜態構造函數 和沒有靜態構造函數執行順序上的差別

 

 
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

        }
    }

到現在為止,我們就暫且告一段落了,當然最后聲明下,上面的這些測試都是一次調用的情況,我們想大家應該知道。其實,上面說了那么一大堆,這個現象的原理是什么,編譯器編譯后他們都有什么不同呢

來看下 下片的介紹:關於 BeforeFieldInit 的東西 : 點我


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM