---
title: "ctorcctor及变量的初始化顺序"
author: "Perrin Yong"
author_profile: https://www.pystone.net/profile/
published_by: "Perrin Yong"
canonical: https://www.pystone.net/notes/csharp-ctor-cctor-init-order/
type: note
content_role: unspecified
visibility: public
id_stability: rename-stable
source_path: "10-计算机、信息技术与工程/02-编程语言与运行时/.NET与CSharp/ctorcctor及变量的初始化顺序.md"
content_hash: 2919b20939bcfc7310aa509b850e6ec00802593021a445f4505017976704f57a
knowledge_version: 224c990773de.5fa8af6e39fa
site_commit: 224c990773de166d23a886306577dd90379529ce
notes_commit: 5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a
---
# ctorcctor及变量的初始化顺序

> 创建时间：2020/4/28 17:37

  * ctorcctor及变量的初始化顺序
    * ctor
      * 默认构造函数
      * 显示定义构造方法
    * cctor
    * 变量初始化顺序概述
    * 总体执行顺序
    * 关于静态字段的初始化
      *         * InitializeOnLoad对Static Constructor的影响
    * 我的实验结果
    * 实验案例
      * Demo1
      * Demo2
      * Demo3

## ctor

当C#的类被编译后，在IL代码中会出现一个名为.ctor的方法，它就是我们的构造函数，对应C#中的 **构造函数** 。

### 默认构造函数

当没有显示定义构造函数时，会自动生成一个构造函数，它没有参数，没有返回值。
它是调用(call)了一个类型为System.Object的实例的.ctor()方法

> 当一个类没有显示声明继承于其它某个类时，它将默认继承自System.Object，并且，在类的构造函数中将会调用其基类的构造方法(.ctor)。

如果在字段声明的同时对其初始化，那么在编译后， **赋值过程将被放到构造方法.ctor中，并且在调用其基类的构造方法之前进行** 。

### 显示定义构造方法

如果类中有显式定义构造方法，那么就不会再自动生成一个无参数无返回值的默认构造方法。
如果在声明字段时同时对其赋值，那么这个赋值过程将在类型的构造方法(.ctor)中最先执行，然后再执行其基类的构造方法，最后才轮到我们显示定义的构造方法体中代码。

## cctor

**类型初始化器** ，是一个静态方法，无参数无返回值，不能直接调用，最多只有一个

```csharp
string a = &quot;test&quot;;
string tmp = &quot;est&quot;;
string b = &quot;t&quot; + tmp;
Console.WriteLine(string .ReferenceEquals(str1, str2));

```

如果在声明静态字段时同时对其赋值，它在编译后会被搬到.cctor中，并且是放在前面，然后才到显式定义的静态构造方法体中的代码。
count在这里会被赋值两次，第一次50，第二次100。

当类C实例化时，会先对声明时就进行赋值的字段赋值，然后调用基类的构造函数，基类再以同样的方法构造自己，一直到顶层的System.Object，然后再回来执行C的显式构造方法中的代码，就是这么一个递归的过程。

## 变量初始化顺序概述

cctor()用来初始化静态变量
ctor()用来初始化动态变量
静态变量一定是在访问这个类之前就初始化好的，至于说是在加载程序集的时候就初始化好耗时在程序访问该类的时候就初始化好，不确定。
但是不重要， 关键是 **确定不同类的静态成员之间不要互相产生依赖** 就OK，因为静态成员变量初始化的先后顺序不太确定。
静态成员初始化的语句只执行一次。

动态的变量和动态构造函数一定是在new的时候才会初始化。

## 总体执行顺序

```csharp
string a = &quot;test&quot;;
string tmp = &quot;est&quot;;
string b = &quot;t&quot; + tmp;
Console.WriteLine(string .ReferenceEquals(str1, str2));

```

类的静态变量 A.cctor()
↓
类的静态构造函数 A.static A()
↓
类的普通变量 A.ctor()
↓
基类的静态变量 Base.cctor()
↓
基类的静态构造函数 Base.static Base()
↓
基类的普通变量 Base.ctor()
↓
基类的构造函数 Base.Base()
↓
类的构造函数 A.A()

## 关于静态字段的初始化

参考文章：
（还未看） <https://csharpindepth.com/Articles/BeforeFieldInit>
<https://www.xuebuyuan.com/197015.html>

**一个类** 的静态字段变量赋值按照它们在相关的类声明中出现的 **文本顺序** 执行。

  * 如果类中存在静态构造函数则静态字段初始值设定项的执行在该 **静态构造函数** **即将执行前** 发生。

  * 否则，静态字段初始值设定项在 **第一次使用该类的静态字段之前先被执行**

  * 而 **静态构造函数** 的执行顺寻 **严格** 按照 **第一次访问该类的顺序** 来进行。

NOTES:

  1. 静态构造函数既 **没有访问修饰符，也没有参数** 。因为是.NET调用的，所以像public和private等修饰符就没有意义了。

  2. 是在创建第一个类实例或任何静态成员被引用时， **.NET将自动调用** 静态构造函数来初始化类，也就是说我们无法直接调用静态构造函数，也就 **无法控制** 什么时候执行静态构造函数了。

  3. 一个类 **只能有一个** 静态构造函数。

  4. 无参数的构造函数可以与静态构造函数共存。尽管参数列表相同，但一个属于类，一个属于实例，所以不会冲突。

  5. 最多 **只运行一次** 。

  6. 静态构造函数 **不可以被继承** 。

  7. 如果没有写静态构造函数，而类中包含带有初始值设定的静态成员，那么编译器会自动生成默认的静态构造函数。

#### InitializeOnLoad对Static Constructor的影响

标注了该属性的静态类，在每次编辑器启动时，虚拟机会自动调用静态构造函数。每次重新编译该类时，以及每次对Game进行Play时，也会调用其Static Constructor。

## 我的实验结果

在Unity中，每次修改代码，都会重新编译。每次重新编译，都相当于一次刷新（类似于Reimport）。
每次编译好之后，静态字段的初始化以及静态构造函数的调用在第一次访问该类的时候执行。访问该类的行为包括：访问该类的静态成员，访问该类的构造函数，调用该类的静态函数等。

## 实验案例

### Demo1

```csharp
string a = &quot;test&quot;;
string tmp = &quot;est&quot;;
string b = &quot;t&quot; + tmp;
Console.WriteLine(string .ReferenceEquals(str1, str2));

```

或者产生如下输出：
Init A
Init B
1 1
或者产生如下输出：
Init B
Init A
1 1
X 的初始值设定项和 Y 的初始值设定项的执行顺序 **无法预先确定** ，上述两种顺序都有可能发生；
唯一能够 **确定** 的是：它们一定会在 **对那些字段的引用之前发生** 。

### Demo2

```csharp
string a = &quot;test&quot;;
string tmp = &quot;est&quot;;
string b = &quot;t&quot; + tmp;
Console.WriteLine(string .ReferenceEquals(str1, str2));

```

所产生的输出必然是：
Init B
Init A
1 1
**静态构造函数** 的执行顺寻 **严格** 按照 **第一次访问该类的顺序** 来进行。
B的静态构造函数（以及B的静态字段初始值设定项）必须在A的静态构造函数和字段初始值设定项之前运行。

### Demo3

参考：<https://www.cnblogs.com/killmyday/archive/2009/10/19/1585938.html>

```csharp
string a = &quot;test&quot;;
string tmp = &quot;est&quot;;
string b = &quot;t&quot; + tmp;
Console.WriteLine(string .ReferenceEquals(str1, str2));

```

B的静态构造函数先被调用，然后是A的静态构造函数被调用。

  * CLR的确是先即时编译（ JIT）B的静态构造函数，然后再即时编译A的静态构造函数。

  * 触发A的静态构造函数的编译是由执行B的静态构造函数的过程所引起的。

  * A 的静态构造函数即时编译完成以后，立即就被调用了，虽然这个时候B的静态构造函数只被执行了一半。

JIT成的汇编代码见博客原文

Ref：
<https://www.cnblogs.com/lizhenlin/p/6586506.html>
<https://www.cnblogs.com/better-than-ever/p/3291520.html>
