摘要: .ctor,.cctor 以及 对象的构造过程.ctor:简述:构造函数,在类被实例化时,它会被自动调用。当C#的类被编译后,在IL代码中会出现一个名为.ctor的方法,它就是我们的构造函数,对应C#中的构造函数。且看下面的代码 ...
 
 

.ctor,.cctor 以及 对象的构造过程

 

.ctor:

简述:构造函数,在类被实例化时,它会被自动调用。

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

public class Class1
{
    private string name;
    private int age;
}

类Class1中没有显示的构造函数,只有两字段,现在用ILDasm.exe打开编译后生成的exe文件,会看到:

可以看到这里有个.ctor,我们没有定义构造函数,但这里却出现了.ctor,这就说明了:

当没有显示定义构造函数时,会自动生成一个构造函数,它没有参数,没有返回值。

那我们来看看这个.ctor都干了什么吧,双击.ctor打开,在弹出的窗口中可以找到下面的几行代码:

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时对其初始化:

public class Class1
{
   private string name = "Lin";
   private int age;
}

再用ILDasm打开生成的exe文件,打开.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

这个跟刚才的相比,多出了红色的那两行,这两行出现在“调用System.Object的构造方法”之前,这说明:

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

现在给上面的C#程序显式加上一个构造方法,它接受两个参数:

public class Class1
{
        private string name = "Lin";
        private int age;

        public Class1(string name, int age)
        {
            this.name = name;
            this.age = age;
        }
}

再用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

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

我们现在先给刚才的代码加上一个静态字段:

public class Class1
{
        private string name = "Lin";
        public static int count = 50;
        private int age;

        public Class1(string name, int age)
        {
            this.name = name;
            this.age = age;
        }
}

再来打开ILDasm来看看:

发现这里多了一个.cctor,它就是类型初始化器,打开它,会看到其中有一句:

IL_0000: ldc.i4.s   50

IL_0002: stsfld     int32 ConsoleApplication1.Class1::count

它对静态字段count进行了赋值,值是50,那么,是.cctor先调用还是.ctor先调用呢?当然是.cctor,它是为初始化类型而生的,专搞静态的东东,而.ctor是构造方法,当然.cctor要先调用了。

现在显示加上一个.cctor,在C#中就是加个静态构造函数,我们不能为其指定访问修饰符(否则编译就会报错):

public class Class1
{
        private string name = "Lin";
        public static int count = 50;
        private int age;

        static Class1()
        {
            count = 100;
        }

        public Class1(string name, int age)
        {
            this.name = name;
            this.age = age;
        }
}

再来看看现在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。

在继承中对象构造过程

看下面这段程序:

    public class A
    {
        public int x = 1;
        public A() { m1(); }
        public void m1() { }
    }

    public class B : A
    {
        public int y = 2;
        public static string sb = "B";
        public B() { m2(); }
        public void m2() { }
    }

    public class C : B
    {
        public int z = 3;
        public static string sc = "C";
        public C() { m3(); }
        public void m3() { }
    }

编译后用ILDasm打开生成的exe文件:

可以看到三者都有一个.ctor,B、C中有.cctor,而A没有,打开B,C的.cctor,可以看到它们都负责初始化自己的静态字段,现在主要来看它们的.ctor。

先看类C的.ctor:

 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

原文:http://www.cnblogs.com/mouhong-lin/archive/2008/05/18/1201747.html

.ctor,.cctor 以及 对象的构造过程的更多相关文章

  1. Java之对象构造过程

    先来运行一段代码 class A { public A() { init(); } public void init() { } public static void main(String[] ar ...

  2. swift 学习- 16 -- 构造过程 02

    // 类的继承 和 构造过程 // 类里面的所有的存储型属性 -- 包括所有继承自父类的属性 -- 都必须在构造过程中设置初始值 // Swift  为类类型提供了 两种构造器来确保实例中所有的存储属 ...

  3. Java内存结构、类的初始化、及对象构造过程

    概述 网上关于该题目的文章已经很多,我觉得把它们几个关联起来讲可能更好理解一下.与其它语言一样,它在执行我们写的程序前要先分配内存空间,以便于存放代码.数据:程序的执行过程其实依然是代码的执行及数据的 ...

  4. 从C++对象内存布局和构造过程来具体分析C++中的封装、继承、多态

    一.封装模型的内存布局 常见类对象的成员可能包含以下元素: 内建类型.指针.引用.组合对象.虚函数. 另一个角度的分类: 数据成员:静态.非静态 成员函数:静态.非静态.虚函数 1.仅包含内建类型的场 ...

  5. Emit学习(2) - IL - 对象的创建过程

    上一篇的介绍中, 并没有介绍到对象的创建过程, 这一篇主要就介绍一下, 对象的创建过程. 其实熟悉了IL语法之后, 完全可以用Reflector反编译代码去查看. 而且正因为有这个工具, 可以对照着R ...

  6. SICP— 第一章 构造过程抽象

    SICP  Structure And Interpretation Of Computer Programs 中文第2版 分两部分  S 和 I 第一章 构造过程抽象 1,程序设计的基本元素 2,过 ...

  7. swift学习笔记之-构造过程

    //构造过程 import UIKit /* 构造过程(Initialization): 1.构造过程是使用类.结构体或枚举类型的一个实例的准备过程.在新实例可用前必须执行这个过程,具体操作包括设置实 ...

  8. JAVA 对象初始化的过程

    对象初始化的过程例:Student S    =    new Student();1.因为new Student()用到了Student类,所以会把它从硬盘上加载进入内存2.如果有static静态代 ...

  9. C++ 构造过程和析构过程

    1.C++构造和析构的过程,类似于穿衣脱衣的过程.穿衣是:先穿内衣,再穿外套.脱衣是:先脱外套,再脱内衣.C++构造过程:首先调用父类构造方法,再调用子类构造方法.C++析构过程:首先调用子类析构方法 ...

随机推荐

  1. HDU 4283 You Are the One

    题意:给定n(n<=100)个人,每个人有个固定的屌丝值D. 起初这些人是站成一行,当第i个人第j个去面试的时候他的值是 Di*j. 要求所有人面试之后 这些值加起来是最小的. 队伍站成一行(其 ...

  2. 动态规划——G 回文串

    G - 回文串 Time Limit:3000MS     Memory Limit:65536KB     64bit IO Format:%I64d & %I64u Submit Stat ...

  3. Detect the Virus - ZOJ 3430(恶心的自动机)

    题目大意:给你一些病毒的特征码,然后再给一些文本,判断每个文本有多少种病毒,不过给的字符串都是加密处理过的,给的每个字符串都有对应一个64以内的一个数(题目里面那个表就是),然后可以把这个64以内的这 ...

  4. poj1042

    题目大意:去捕鱼 约翰去参加一个垂钓旅行,他有h小时可以使用在该地区有n (2 <= n <= 25) 个湖泊可以沿着一个单一的路到达,约翰从湖泊1开始,但是它可以在任何湖泊结束他如果想, ...

  5. 423. Reconstruct Original Digits from English

    这个题做得突出一个蠢字.. 思路就是看unique letter,因为题里说肯定是valid string.. 一开始有几个Z就有几个ZERO 同样的还有x for six, g for eight, ...

  6. [Angular 2] Custom Validtors

    Create a custom validtor which only accepts the string start with '123'; function skuValidator(contr ...

  7. U磁盘检测和大量收集量产工具

    1.ChipGenius ChipGenius是一款USB设备芯片型号检測工具,能够自己主动查询U盘.MP3/MP4.读卡器.移动硬盘等一切USB设备的主控芯片型号.制造商.品牌.并提供相关资料下载地 ...

  8. 查看Linux声卡基本信息[转载]

    查看系统版本~$ uname -aLinux laptop 2.6.27-7-generic #1 SMP Tue Nov 4 19:33:20 UTC 2008 i686 GNU/Linux 查看型 ...

  9. UIDevice 系统信息详解

    获取当前设备    //获取当前设备    UIDevice *dev = [UIDevice currentDevice]; 设备和系统基本信息    NSLog(@"设备名称:%@&qu ...

  10. Android(java)学习笔记233: 远程服务的应用场景(移动支付案例)

    一. 移动支付:       用户需要在移动终端提交账号.密码以及金额等数据 到 远端服务器.然后远端服务器匹配这些信息,进行逻辑判断,进而完成交易,返回交易成功或失败的信息给移动终端.用户提交账号. ...