C# 静态构造函数,静态变量执行顺序(升华版)

上篇 是基本语法基础下的执行顺序,包括继承这个维度下的执行顺序,我们可以依照的规律顺下来,下面我们看下一些摸不到头脑的情况

我们实验 一个 类中的方法 去调用另一个非继承类的情况,  我们主要看下  静态构造函数 和没有静态构造函数执行顺序上的差别

 ① 开始实验和观察

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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定义一个静态构造函数。然后再看下顺序会是什么样的:


 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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类加个静态构造函数,现在看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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中,在初始化静态成员的时候,再去调第三个类的静态成员呢:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.....
 
 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加个静态构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
.....
 
   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
    }
 
......

依然符合。结论依然牢固

还要继续玩吗,继续新的怀疑

如果我们把不被别的地方调的静态成员变成私有的呢,会不会影响顺序了,这个我这里就不列了,答案是:不会

继续,总觉的还有没有挖掘的东西需要一探究竟

我们突然发现,上面我们调的都是另一个类的静态字段,如果是调的另一个类的静态方法呢,会不会也是这个规律呢

改造起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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那个静态字段,那效应就会出来了:

1
2
3
4
5
6
7
8
9
10
11
12
13
.......
 
 static void Main(string[] args)
        {                             
             
             People.xixi(); 
            People.Instance.OutDemo();     //有这个 就会先判断People有没有静态构造函数,来决定是否在进main方法前先 预先跳转
 
 
            Console.ReadKey();
        }
 
......<br>

如果对调用层做个包装呢

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
....
  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的静态字段再改下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
......
 
   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加个静态构造函数呢?你们可以试试,我就不展示了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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 的东西 : 点我

最后我们来一个大的回顾和总结:

 

 

 

 

 ③ 实验大练兵

我们开始练习

  首先

 

 进一步的★★

 

 再进一步的★★★★★

 

 

 ③ 逻辑图

 

 

 

 

 

 

 

 

 

 

 

 
posted @   凯帝农垦  阅读(1375)  评论(4编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示