static const readonly 究竟是什么?

参考资料:
《C# 7.0 本质论》
B站杨旭C#教程:https://www.bilibili.com/video/BV1k4411H7aM?p=4

下面的一部分内容是来自《C# 7.0 本质论》,还有一部分是我直接把杨旭C#教程的PPT手打了一遍,为了促进我的思考。建议朋友们直接去看他的教程,还辅以他的一些讲解,更加易懂。

什么是static

C#没有全局变量或全局函数,在C#中与全局字段或函数等价的是静态字段或方法。“全局变量/函数”和“C#静态字段/方法”在功能上没有差异,只是静态字段/方法可包含访问修饰符,比如private,从而限制访问并提供更好的封装。

什么是静态字段

  • 使用static关键字定义能由同一个类的多个实例共享的数据
  • 和实例字段(非静态字段)一样,静态字段也可在声明时初始化。
  • 和实例字段不同,未初始化的静态字段将获得默认值(0,null,false等),所以没有显式赋值的静态字段也是可以访问的
  • 变量的作用域是可以不加限定来引用的程序代码区域
  • 静态字段的作用域是类(及其任何派生类)

注意,在设计对象时,程序员要考虑字段和方法应声明为静态还是基于实例。

  • 将不需要访问任何实例数据的方法声明为静态方法
  • 将需要访问实例数据的方法(实例不作为参数传递)声明为实例方法
  • 静态字段主要存储对应于类的数据,比如新实例的默认值或者已创建实例个数
  • 实例字段主要存储和对象关联的数据

例如,我们需要记录到现在一共创建过多少个该对象的实例,就可以用静态字段。

什么是const(常量)

  • 一个值不可以改变的静态字段
  • 在编译时值就已经定下来了
  • 任何使用常量的地方,编译器都会把这个常量替换为它的值
  • 常量的类型可以是内置的数值类型、bool、char、string或enum
  • 使用const关键字声明,生命的同时必须使用具体的值来对其初始化

示例:

public class Test
{
    public const string Message = "Hello World!";
}

设计规范:

  • 要为永远不变的值使用常量字段
  • 不要为将来会发生变化的值使用常量字段

什么是readonly

  • 和const不同,readonly修饰符只能用于字段,不能用于局部变量
  • 它指出字段值只能从构造函数中更改,或在声明时通过初始化器指定

原来只读(readonly)的字段是可以修改的,不过只能从构造函数中更改,或在声明时通过初始化器指定。const的不可以修改。

常量(const)与静态只读字段的区别

  • 常量比静态只读字段更严格:
    • 可使用的类型(const更少一些)
    • 字段初始化的语义上
  • 常量是在编译时进行值的估算

QQ截图20200614114054.png

常量编译时直接把具体的值带进来了,运行时直接运行具体的值。

注意:
当值有可能改变,并且与需要暴露给其它Assembly(程序集)的时候,静态只读(static、readonly)字段是相对较好的选择。

public const decimal ProgramVersion = 2.3;

如果Y Assembly引用了X Assembly并且使用了这个常量,那么在编译的时候,2.3这个值就会被固化于Y Assembly里。这意味着,如果后来X重编译了,这个常量变成了2.4或其它值,如果Y不重新编译的话,Y将仍然使用2.3这个值,直到Y被重新编译,它的值才会变成2.4.静态只读字段就会避免这个问题的发生

另外,方法可以有本地的常量:

static void Main()
{
    const double twoPI = 2 * System.Math.PI;
    ...
}

什么是静态构造函数

  • 静态构造函数,每个类执行一次

  • 非静态构造函数,每个实例执行一次

  • 一个类型只能定义一个静态构造函数

    • 必须无参
    • 方法名与类型一致
    class Test
    {
        static Test() { Console.WriteLine("Type Initialized"); }
    }
    
  • 在类型使用之前的一瞬间,编译器会自动调用类型的静态构造函数:

    • 实例化一个类型
    • 访问类型的一个静态成员
  • 只允许使用 unsafe 和 extern 修饰符

注意,如果静态构造函数抛出了未处理的异常,那么这个类型在该程序的剩余生命周期内将无法使用了。

静态字段和静态构造函数的初始化顺序

  • 静态字段的初始化器在静态构造函数被调用之前的一瞬间运行
  • 如果类型没有静态构造函数,那么静态字段初始化器在类型被使用之前的一瞬间执行,或者在运行时突发奇想的时候执行
  • 静态字段的初始化顺序与它们的声明顺序一致
class Foo
{
    public static int X = Y; // 0
    public static int Y = 3; // 3
}

例子:

class Program
{
    static void Main() { Console.WriteLine(Foo.X); } // 3
}

class Foo
{
    public static Foo Instance = new Foo();
    public static int X = 3;

    Foo() { Console.WriteLine(X); } // 0 
}

Program中,使用了类型Foo,在类型使用之前的一瞬间,初始化了Foo类中第二行的静态字段X,所以Program中输出3。

Foo中,静态字段的初始化顺序使按照它们声明的顺序决定的,即先初始化Instance,new Foo(),直接调用了该构造函数,此时X还未被初始化为3,所以输出X的值为int型的默认值 0。

什么是静态属性

属性也可声明为static,使用静态属性几乎肯定比使用公共静态字段好,因为公共静态字段在任何地方都能调用,而静态属性至少提供了一定程度的封装。

什么是静态类

  • 类也可以是静态的
  • 其成员必须全是静态的
  • 不可以有子类
  • 例如
    • System.Console
    • System.Math

什么是Finalizer(终结器或析构函数)

  • Finalizer是class专有的一种方法
  • 在GC回收未引用对象的内存之前运行
  • 其实就是对object的Finalize()方法重写的一种语法
class Class1
{
    ~Class1()
    {
        ...
    }
}

这个东西我学C++的时候好像翻译成析构函数,在开发中基本没用过。

感受

static和const是自从我开始学编程以来就一直困扰着我的东西,因为平常不怎么用,就一直没去研究。现在我依旧不是很理解它们。希望以后可以随着我的编程经验的增加,来慢慢理解它们。

原文地址:https://www.cnblogs.com/Kit-L/p/13128742.html