2020年6月26日星期五

关于C#中静态的一点认识

1.静态直观的特点

  静态成员最显著的一个特点就是它的作用域是全局的。只要在调用处引入了对应的命名空间,那么我们可以在代码任何地方都可以直接使用。凡是具有全局特征的东西我们就可以考虑使用静态。在实际的开发中,静态字段我们常用实现数据的共享,修饰为静态的方法当做常用的工具方法来使用。

 

2.命名上的思考

  静态从名称的对立面上可以想到一个词叫动态,这里所说的动态可以隐喻为实例成员。实例成员之所以可以形容为动态因为在内存中它的创建和释放资源是根据实例化对象或GC(垃圾回收)不断变化的。而静态成员在内存中很少会发生变化,静态成员的创建是在第一次访问对应的类型时进行的创建,资源释放是在应用程序结束的时候。

 

3.静态和实例(非静态)的对比

静态

实例(非静态)

需要static关键字

不需要static关键字

无法实例化只能使用类名调用

必须实例化对象后才能调用

在静态方法中,可以访问静态成员

在实例方法中,可以访问静态成员

在静态方法中不可以直接访问实例成员,如需访问需要创建对象

在实例方法中,可以直接访问实例成员

调用前初始化(在创建第一个实例或引用任何静态成员之前)

实例化(调用构造函数)对象时初始化

 

4.静态成员的初始化的创建过程(本文重点)

当一个类当中包含了静态成员时,程序内部是如何初始化静态成员的呢?

4.1代码示例说明1

 如运行上图的程序,对于这种情况实际上即使是在程序运行时该类中的静态成员是没有被创建(初始化的)。之所以是这种情况,因为程序中静态成员对应的类型并没有在任何位置出现过访问该类型的代码。而静态成员的创建是在第一次访问这个类型时创建的。

 

4.2代码示例说明2

   在遵循静态成员的创建原则后,此图对代码进行改动后包含了对类型的访问,那么这个时候静态成员是会被创建的。

  从直观的代码上来看,很显然会产生一个对静态成员初始化的误解,因为代码中的静态成员在声明时已经进行初始化赋值的操作。其实这种方式只是一种语法糖,是为了方便程序员给的简单语法。实际上对于类型内部的字段赋值是通过构造函数来完成的。

  我们现在思考一个问题:上图代码中并没有编写任何的构造函数,那么静态成员是如何进行初始化赋值的呢?

  通过一般的调试时并看不出程序内部的执行过程,也很难找寻问题的答案。此处为了探寻上述的问题我可以通过反编译工具查看下上图中反编译后的代码。如图:

   上图中我们可以看到通过反编译后MyClass内部包含了一个静态的构造函数并且为静态成员进行赋值。此处可以得到一个结论:当在类中定义静态成员的时候,实际上也定义了一个静态构造函数。

  

  根据反编译代码的情况我们将静态构造函数显示的写在代码中调试,来验证下静态构造函数是否被调用,然后观察下实例成员和静态成员初始化的顺序。如图:

  

   根据上图中的演示可以看出静态构造函数被调用,并且会先调用静态构造函数然后在调用实例构造函数。

 

5.关于静态构造函数需要注意强调的几点

  1. 静态构造函数的定义上:不能有访问修饰符并且必须无参,一个类或结构当中只能有一个构造函数,不允许重载和继承。
  2. 静态构造函数调用上:静态构造函数在编写代码时不能像实例构造函数那样直接调用,它的调用是由公共语言运行时(CLR)调用。并且静态构造函数只会调用一次。

以下代码运行图验证静态构造函数只会调用一次的情况:

 

使用建议:

  尽量少使用静态成员,因为静态成员的生命周期是在应用程序结束后释放的,然而GC(垃圾回收)没有对其进行内存的回收管理。促使会上时间的占用内存,并且会产生一些内存垃圾。

学习感想:

   看似使用简单的静态特性,有人运用设计出了软件模式的单例模式。淬炼坚实的基本功永远都不算晚,个人觉得编程最难的是在运用,就算学会在高端的技术如果不会组合运用,也还是没有达到真正掌握的程度。

 

关于C#中静态的一点认识