.ctor,.cctor 以及 對象的構造過程


摘要: .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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM