由一個Quiz想到的


CSDN社區C#版有人出基礎題,我沒事也做一下練練,今天做到一道題是這樣的:
(原題鏈接: http://community.csdn.net/Expert/topic/3918/3918948.xml?temp=.2294428)

編寫一個控制台應用程序,完成下列功能,並回答提出的問題。
1.創建一個類A,在構造函數中輸出“A”,再創建一個類B,在構造函數中輸出“B”。
2.從A繼承一個名為C的新類,並在C內創建一個成員B。不要為C創建構造函數。
3.在Main方法中創建類C的一個對象,寫出運行程序后輸出的結果。
4.如果在C中也創建一個構造函數輸出“C”,整個程序運行的結果又是什么?

要簡單回到這道題是簡單的,我的答案是:
 1 using  System;
 2
 3 class  A
 4 {
 5    public A()
 6    {
 7        Console.WriteLine("A");
 8    }

 9}

10
11 class  B
12 {
13    public B()
14    {
15        Console.WriteLine("B");
16    }

17}

18
19 class  C : A
20 {
21    public C()
22    {
23        Console.WriteLine("C");
24    }

25    B b = new B();
26}

27
28 class  Test
29 {
30    public static void Main()
31    {
32        C c = new C();
33        
34    }

35}

跟作者的參考答案是一致的,但是作者在結帖的時候並沒有回答有人提出的一個問題:問什么是這樣的輸出順序呢?

我就自己想了一下。首先看輸出解結果“B”是調用B類的實例構造函數輸出的,“A”是調用A類的構造函數輸出的,“C”調用C類的構造函數輸出的。
其中B類作為一個字段出現在C類中,而C類繼承A類。那么,我猜,在主函數中構造C對象,首先初始化C類的字段,然后調用祖先類的構造函數,再實現C類自己構造函數中的代碼,這樣就解釋了這種順序。

那么這種猜想成不成立呢?

首先初始化C類的字段可能會有些疑惑,為什么非要先初始化類的字段呢?這里就扯到另外一個問題:字段的內聯初始化是在哪里初始化的(這里的內聯是指聲明字段時同時進行初始化賦值,比如public int i = 1,而不是public in i),在《.NET框架程序設計》9.1節實例構造器中有解釋,這里我只說結果,就是內聯初始化實際上是在類型的構造器中完成的。那么這樣看來,初始化B類對象b跟C中的Console.WriteLine()方法都發生在C類的構造器中,其實調用父類A的構造器也發生在C的構造器,至於為什么非要把內聯初始化字段放在最前面,我就不清楚了,還請大家指點(可能有什么先搞定字段,再搞方法的規則吧,哈哈)。不過這個順序是沒錯的,有IL作證:
 1 .method  public  hidebysig specialname rtspecialname 
 2         instance  void   .ctor() cil managed
 3 {
 4  // 代碼大小       28 (0x1c)
 5  .maxstack  2
 6  IL_0000:  ldarg.0
 7  IL_0001:  newobj     instance void B::.ctor()
 8  IL_0006:  stfld      class B C::b
 9  IL_000b:  ldarg.0
10  IL_000c:  call       instance void A::.ctor()
11  IL_0011:  ldstr      "C"
12  IL_0016:  call       void [mscorlib]System.Console::WriteLine(string)
13  IL_001b:  ret
14}
  //  end of method C::.ctor

以上是C構造函數的IL代碼,我們可以清楚地看到首先創建可B的實例b,當然同時調用了B的構造函數,然后把b值存為C的對象的一個字段,然后調用A的構造函數,然后在調用WriteLine方法。

可能會有人對B b = new B()是內聯初始化不太理解,那么我再添加以行代碼int i = 1;到后面,使C類變為:
class  C : A
{
    
public C()
    
{
        Console.WriteLine(
"C");
    }

    B b 
= new B();
    
int i = 1;
}


那么IL就變成:
.method  public  hidebysig specialname rtspecialname 
        instance 
void   .ctor() cil managed
{
  
// 代碼大小       35 (0x23)
  .maxstack  2
  IL_0000:  ldarg.
0
  IL_0001:  newobj     instance 
void B::.ctor()
  IL_0006:  stfld      
class B C::b
  IL_000b:  ldarg.
0
  IL_000c:  ldc.i4.
1
  IL_000d:  stfld      int32 C::i
  IL_0012:  ldarg.
0
  IL_0013:  call       instance 
void A::.ctor()
  IL_0018:  ldstr      
"C"
  IL_001d:  call       
void [mscorlib]System.Console::WriteLine(string)
  IL_0022:  ret
}
  //  end of method C::.ctor

你看,這回比較清楚了吧,字段的初始化是在前面,同時說明了一個問題,就是,要避免內聯初始化字段,這樣會增加代碼尺寸,把初始化放構造器中去進行, 這里不再細述。

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com