惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threatpost
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
V
Visual Studio Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Forbes - Security
Forbes - Security
人人都是产品经理
人人都是产品经理
N
Netflix TechBlog - Medium
Recent Commits to openclaw:main
Recent Commits to openclaw:main
WordPress大学
WordPress大学
Webroot Blog
Webroot Blog
Jina AI
Jina AI
H
Hacker News: Front Page
Attack and Defense Labs
Attack and Defense Labs
T
Troy Hunt's Blog
TaoSecurity Blog
TaoSecurity Blog
AI
AI
Hacker News - Newest:
Hacker News - Newest: "LLM"
Google Online Security Blog
Google Online Security Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Help Net Security
Help Net Security
美团技术团队
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 叶小钗
P
Privacy International News Feed
A
Arctic Wolf
IT之家
IT之家
云风的 BLOG
云风的 BLOG
S
Security Affairs
Simon Willison's Weblog
Simon Willison's Weblog
The Cloudflare Blog
博客园 - 司徒正美
Vercel News
Vercel News
C
Cyber Attacks, Cyber Crime and Cyber Security
SecWiki News
SecWiki News
K
Kaspersky official blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
N
News and Events Feed by Topic
S
Schneier on Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
有赞技术团队
有赞技术团队
PCI Perspectives
PCI Perspectives
F
Fortinet All Blogs
T
Tenable Blog
Spread Privacy
Spread Privacy
T
The Blog of Author Tim Ferriss
S
Securelist
L
LangChain Blog
Latest news
Latest news
Cloudbric
Cloudbric
博客园 - 三生石上(FineUI控件)

博客园 - 水言木

好久没来了,发个招聘贴 RavenDB Tips: 巧妙设计文档的Id格式以提升查询性能 CQRS实践(4): 领域事件 CQRS实践(3): Command执行结果的返回 CQRS实践(2): Command的实现 CQRS实践(1): 什么是CQRS 准备重新开始写点东西 不要让编写set访问器成为习惯 [PoEAA] Learning Notes of Data Transfer Object 再谈“引用” 使用UrlRewriter进行Url重写的完整解决方案 迅速搭建SVN环境 小议GridView中的删除按钮 工厂的那些事儿 关于Ajax的只言片语[2008.7.31最后更新] 重温:从aspx到dll 小记一次让我误会的引用传参 关于ASP.NET的只言片语[2008.8.12最后更新] 汶川地震小感
.ctor,.cctor 以及 对象的构造过程
水言木 · 2008-05-18 · via 博客园 - 水言木

.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