最近才知道struct和class的静态构造函数的触发规则是不同的,不像class在第一次使用类的时候触 发静态构造函数。如果只访问struct实例的字段是不会触发静态构造函数调用的。通过测试发现当访问静 态字段,struct本身的函数(静态和实例)和带参数的构造函数就会引起静态构造函数的执行。而调用默 认构造和未覆写的基类虚函数是不会的。为什么呢?
让我们先来看看class和struct在调用构造函数时的区别。class使用newobj指令而struct使用initobj 指令来构造对象。newobj在堆上申请一块内存并调用相应的构造函数进行初始化,然后将对象地址返回给 计算栈。initbobj则是从本地变量表中载入已经分配出来的struct实例然后初始化struct的各字段。这个 初始化过程是CLR内部执行的,而不像class编译器会给class添加一个默认构造函数(这就是为什么 struct不能给字段添加默认值的原因。但在类中如果给字段添加了默认值编译器就会自动在构造函数中添 加字段赋值操作)。如果给struct中定义了一个有参数的构造函数,那么系统就不会使用initobj指令, 而是直接用call指令调用带参数的构造函数。
我们最常见最常用的调用函数的指令是call和callvirt。对于静态函数使用call指令,对于class使用 callvirt指令(不论class中的函数是不是虚的)。只有子类调用父类的函数的时候(避免递归调用)以 及构造函数中(由编译器添加保证父类字段被初始化)使用call指令。而对于struct我们发现只要调用的 函数是struct本身定义的都是使用call指令。call和callvirt指令的差别在于,call会把调用的函数当作 静态函数看待,而不会关心调用当前函数时实例指针(this)是否为空。这就是struct调用函数时为什么 都是call因为struct实例是不可能被置为null的。实际上class在调用非虚函数时实际上也是使用call的 只是多做了一步验证——this是否为空,让我们来验证一下。
class Class_Test{ public void Test1() {} public virtual void Test2()
{} public static void Test3() {} public override string ToString()
{ return base.ToString(); }}Class_Test c = new Class_Test
();c.Test1();c.Test2();Class_Test.Test3();string str = c.ToString();