.ctor:
简述:构造函数,在类被实例化时,它会被自动调用。
当C#的类被编译后,在IL代码中会出现一个名为.ctor的方法,它就是我们的构造函数,对应C#中的构造函数。且看下面的代码:
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret 上面就是这个.ctor的方法体,看上面的红色行,从字面上可以看出,它是调用(call)了一个类型为System.Object的实例的.ctor()方法,从这就可以证明:
当一个类没有显示声明继承于其它某个类时,它将默认继承自System.Object,并且,在类的构造函数中将会调用其基类的构造方法(.ctor)。 现在对上面的程序小改一下,在声明name时对其初始化:再用ILDasm打开生成的exe文件,打开.ctor,里面有这么几行:
IL_0000: ldarg.0IL_0001: ldstr "Lin"
IL_0006: stfld string ConsoleApplication1.Class1::name
IL_000b: ldarg.0
IL_000c: call instance void [mscorlib]System.Object::.ctor()
IL_0011: nop 这个跟刚才的相比,多出了红色的那两行,这两行出现在“调用System.Object的构造方法”之前,这说明:如果在字段声明的同时对其初始化,那么在编译后,赋值过程将被放到构造方法.ctor中,并且在调用其基类的构造方法之前进行。现在给上面的C#程序显式加上一个构造方法,它接受两个参数:
再用ILDasm打开exe时,会发现有了点变化:
这里的.ctor带了两参数,一个string类型,一个int32类型,而刚才的无参无返回值的.ctor不见了,这也证明了:如果类中有显式定义构造方法,那么就不会再自动生成一个无参数无返回值的默认构造方法。 打开.ctor,会看到其中有这么几行:IL_0000: ldarg.0
IL_0001: ldstr "Lin"
IL_0006: stfld string ConsoleApplication1.Class1::name
IL_000b: ldarg.0
IL_000c: call instance void [mscorlib]System.Object::.ctor()
IL_0011: nop
IL_0012: nop
IL_0013: ldarg.0
IL_0014: ldarg.1
IL_0015: stfld string ConsoleApplication1.Class1::name
IL_001a: ldarg.0
IL_001b: ldarg.2
IL_001c: stfld int32 ConsoleApplication1.Class1::age
IL_0021: nop 从上面红色标识的代码的顺序中,我们可以进一步得到:如果在声明字段时同时对其赋值,那么这个赋值过程将在类型的构造方法(.ctor)中最先执行,然后再执行其基类的构造方法,最后才轮到我们显示定义的构造方法体中代码。
.cctor
简述:类型初始化器,是一个静态方法,无参数无返回值,不能直接调用,最多只有一个
我们现在先给刚才的代码加上一个静态字段:IL_0002: stsfld int32 ConsoleApplication1.Class1::count 它对静态字段count进行了赋值,值是50,那么,是.cctor先调用还是.ctor先调用呢?当然是.cctor,它是为初始化类型而生的,专搞静态的东东,而.ctor是构造方法,当然.cctor要先调用了。 现在显示加上一个.cctor,在C#中就是加个静态构造函数,我们不能为其指定访问修饰符(否则编译就会报错):
再来看看现在ILDasm下的.cctor,其中有几行:
IL_0000: ldc.i4.s 50
IL_0002: stsfld int32 ConsoleApplication1.Class1::count
IL_0007: nop
IL_0008: ldc.i4.s 100
IL_000a: stsfld int32 ConsoleApplication1.Class1::count
可以看到:
如果在声明静态字段时同时对其赋值,它在编译后会被搬到.cctor中,并且是放在前面,然后才到显式定义的静态构造方法体中的代码,也就是说count在这里会被赋值两次,第一次50,第二次100。在继承中对象构造过程
看下面这段程序:
IL_0001: ldc.i4.3
IL_0002: stfld int32 ConsoleApplication1.C::z
IL_0007: ldarg.0
IL_0008: call instance void ConsoleApplication1.B::.ctor()
IL_000d: nop
IL_000e: nop
IL_000f: ldarg.0
IL_0010: call instance void ConsoleApplication1.C::m3()
可以看到: 在C被实例化时,它最先初始化在声明时同时赋值的字段(非静态),此处是将3赋给z,然后它会调用其基类的.ctor(),然后再调用自己的实例方法m3(),值得注意的是,在执行显式定义的构造方法体中的代码前,会先调用其基类的构造方法(创建基类的实例)。再来看类B的.ctor():IL_0001: ldc.i4.2
IL_0002: stfld int32 ConsoleApplication1.B::y
IL_0007: ldarg.0
IL_0008: call instance void ConsoleApplication1.A::.ctor()
IL_000d: nop
IL_000e: nop
IL_000f: ldarg.0
IL_0010: call instance void ConsoleApplication1.B::m2()
同样,我们可以看到,在实例化B时,它会先把2赋给自己的y,然后再调用基类A的构造方法,最后再调用自己的实例方法m2()。 那A的.ctor()就不再看了,可以猜到它一定是在做这样的事: 1、 将1赋给实例的x字段; 2、 调用基类System.Object的构造方法.ctor来创建基类的实例; 3、 调用实例方法m1();总结
1、.ctor是构造方法;
2、.cctor是类型初始化器,在C#中也就是静态构造函数; 3、当类C实例化时,会先对声明时就进行赋值的字段赋值,然后调用基类的构造函数,基类再以同样的方法构造自己,一直到顶层的System.Object,然后再回来执行C的显式构造方法中的代码,就是这么一个递归的过程。参考资料
1、《Essential .NET》 Volume 1
原文: