.ctor,.cctor 以及 對象的構(gòu)造過程
.ctor:
簡述:構(gòu)造函數(shù),在類被實例化時,它會被自動調(diào)用。
當(dāng)C#的類被編譯后,在IL代碼中會出現(xiàn)一個名為.ctor的方法,它就是我們的構(gòu)造函數(shù),對應(yīng)C#中的構(gòu)造函數(shù)。且看下面的代碼:
public class Class1
{
private string name;
private int age;
}
類Class1中沒有顯示的構(gòu)造函數(shù),只有兩字段,現(xiàn)在用ILDasm.exe打開編譯后生成的exe文件,會看到:

可以看到這里有個.ctor,我們沒有定義構(gòu)造函數(shù),但這里卻出現(xiàn)了.ctor,這就說明了:
當(dāng)沒有顯示定義構(gòu)造函數(shù)時,會自動生成一個構(gòu)造函數(shù),它沒有參數(shù),沒有返回值。
那我們來看看這個.ctor都干了什么吧,雙擊.ctor打開,在彈出的窗口中可以找到下面的幾行代碼:
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: ret
上面就是這個.ctor的方法體,看上面的紅色行,從字面上可以看出,它是調(diào)用(call)了一個類型為System.Object的實例的.ctor()方法,從這就可以證明:
當(dāng)一個類沒有顯示聲明繼承于其它某個類時,它將默認(rèn)繼承自System.Object,并且,在類的構(gòu)造函數(shù)中將會調(diào)用其基類的構(gòu)造方法(.ctor)。
現(xiàn)在對上面的程序小改一下,在聲明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
這個跟剛才的相比,多出了紅色的那兩行,這兩行出現(xiàn)在“調(diào)用System.Object的構(gòu)造方法”之前,這說明:
如果在字段聲明的同時對其初始化,那么在編譯后,賦值過程將被放到構(gòu)造方法.ctor中,并且在調(diào)用其基類的構(gòu)造方法之前進行。
現(xiàn)在給上面的C#程序顯式加上一個構(gòu)造方法,它接受兩個參數(shù):
public class Class1
{
private string name = "Lin";
private int age;
public Class1(string name, int age)
{
this.name = name;
this.age = age;
}
}
再用ILDasm打開exe時,會發(fā)現(xiàn)有了點變化:

這里的.ctor帶了兩參數(shù),一個string類型,一個int32類型,而剛才的無參無返回值的.ctor不見了,這也證明了:
如果類中有顯式定義構(gòu)造方法,那么就不會再自動生成一個無參數(shù)無返回值的默認(rèn)構(gòu)造方法。
打開.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
從上面紅色標(biāo)識的代碼的順序中,我們可以進一步得到:
如果在聲明字段時同時對其賦值,那么這個賦值過程將在類型的構(gòu)造方法(.ctor)中最先執(zhí)行,然后再執(zhí)行其基類的構(gòu)造方法,最后才輪到我們顯示定義的構(gòu)造方法體中代碼。
.cctor
簡述:類型初始化器,是一個靜態(tài)方法,無參數(shù)無返回值,不能直接調(diào)用,最多只有一個
我們現(xiàn)在先給剛才的代碼加上一個靜態(tài)字段:
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來看看:

發(fā)現(xiàn)這里多了一個.cctor,它就是類型初始化器,打開它,會看到其中有一句:
IL_0000: ldc.i4.s 50
IL_0002: stsfld int32 ConsoleApplication1.Class1::count
它對靜態(tài)字段count進行了賦值,值是50,那么,是.cctor先調(diào)用還是.ctor先調(diào)用呢?當(dāng)然是.cctor,它是為初始化類型而生的,專搞靜態(tài)的東東,而.ctor是構(gòu)造方法,當(dāng)然.cctor要先調(diào)用了。
現(xiàn)在顯示加上一個.cctor,在C#中就是加個靜態(tài)構(gòu)造函數(shù),我們不能為其指定訪問修飾符(否則編譯就會報錯):
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;
}
}
再來看看現(xiàn)在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
可以看到:
如果在聲明靜態(tài)字段時同時對其賦值,它在編譯后會被搬到.cctor中,并且是放在前面,然后才到顯式定義的靜態(tài)構(gòu)造方法體中的代碼,也就是說count在這里會被賦值兩次,第一次50,第二次100。
在繼承中對象構(gòu)造過程
看下面這段程序:
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,可以看到它們都負(fù)責(zé)初始化自己的靜態(tài)字段,現(xiàn)在主要來看它們的.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被實例化時,它最先初始化在聲明時同時賦值的字段(非靜態(tài)),此處是將3賦給z,然后它會調(diào)用其基類的.ctor(),然后再調(diào)用自己的實例方法m3(),值得注意的是,在執(zhí)行顯式定義的構(gòu)造方法體中的代碼前,會先調(diào)用其基類的構(gòu)造方法(創(chuàng)建基類的實例)。
再來看類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()
那A的.ctor()就不再看了,可以猜到它一定是在做這樣的事:
1、 將1賦給實例的x字段;
2、 調(diào)用基類System.Object的構(gòu)造方法.ctor來創(chuàng)建基類的實例;
3、 調(diào)用實例方法m1();
總結(jié)
1、.ctor是構(gòu)造方法;
2、.cctor是類型初始化器,在C#中也就是靜態(tài)構(gòu)造函數(shù);
3、當(dāng)類C實例化時,會先對聲明時就進行賦值的字段賦值,然后調(diào)用基類的構(gòu)造函數(shù),基類再以同樣的方法構(gòu)造自己,一直到頂層的System.Object,然后再回來執(zhí)行C的顯式構(gòu)造方法中的代碼,就是這么一個遞歸的過程。
參考資料
1、《Essential .NET》 Volume 1


浙公網(wǎng)安備 33010602011771號