C#成員的初始化順序你真的非常清楚嗎,我發現有點坑爹,坑到爹突然有點搞不清楚什麼狀況。下面咱們開始分析,先看3個簡單類。
[csharp]
public abstract class Base
{
public Base()
{
SetValue();
}
public abstract void SetValue();
}
public class Sub : Base
{
public string value;
public Sub()
{
value = "chentaihan";
}
public override void SetValue()
{
value = "陳太漢";
}
}
public class Sub1 : Base
{
public string value = "chentaihan";
public override void SetValue()
{
value = "陳太漢";
}
}
如果執行下面這段代碼會輸出什麼值呢,請不要往下看,先給出你自己的答案。
[csharp]
static class Program
{
static void Main(string[] args)
{
Sub sub = new Sub();
Console.WriteLine(sub.value);
Sub1 sub1 = new Sub1();
Console.WriteLine(sub1.value);
Console.Read();
}
}
是的他很簡單,但你確信你的答案就是對的嗎?這麼一個簡單的問題我答錯了,所以就有了這篇博客。 CLR VIA C#這本書告訴我們:成員在定義的時候初始化相當於在構造函數的最上面初始化,如果一個成員在定義的時候初始化,並在構造函數中賦值,那麼在構造函數執行完成之後,該成員的值就是造函數中所賦的值,所以我得出的答案都是:chentaihan。但答案不是這樣的。當運行結果出來時,我那個迷茫啊.....。
先來說說我的簡單分析:
1:進入子類構造函數
2:Sub成員變量的內存被分配
3:調用父類構造函數
4:調用子類的方法SetValue(子類覆寫了這個方法),value被賦值
5:正式執行子類構造函數,成員變量value再次被賦值 www..2cto.com
從上面5步我得出他們輸出的結果一樣,都是chentaihan。錯在哪裡呢?
於是我用Reflector查看了一下,得到的結果正如上面所說,他們的源碼是一樣的,如下所示。正如CLR VIA C#這本書說的那樣,那為什麼結果不一樣呢,Reflector代碼是一樣的,執行的結果卻不一樣,怎麼回事,怎麼回事,那我只能說Reflector坑爹,它不能反映程序的真正執行邏輯,非要我用IL,我用的還不熟呢。
[csharp]
public class Sub : Base
{
public string value;
public Sub()
{
value = "chentaihan";
}
public override void SetValue()
{
value = "陳太漢";
}
}
神馬情況,他們的IL代碼是不一樣的,如圖所示
看了這個圖,我們知道答案是chentaihan,陳太漢。誰能告訴我怎麼調用父類的構造函數和給value賦值的順序不一樣啊。該用的工具都用了,我該怎麼證明這個結果,於是開始單步調試,於是發現了一個每天都發現了的秘密:成員初始化在構造函數之前執行。難怪這本書上說成員在定義的時候初始化相當於在構造函數的最上面初始化,Reflector也證實了這個答案。但是又繞進另一個坑爹的問題:構造函數還沒有調用,內存還沒有分配,怎麼給成員變量賦值啊?這不是問題,從上圖可以看出成員變量的賦值只是在父類的構造函數之前調用,肯定也是在子類的成員變量分配空間之後為成員變量賦值。好的,最後我們得出的結論是:
1:進入子類構造函數
2:Sub成員變量的內存被分配
3:為Sub成員變量賦值
4:調用父類構造函數
5:調用子類的方法SetValue(子類覆寫了這個方法),value被賦值
6:正式執行子類構造函數,成員變量value再次被賦值
這樣的解釋答案就很合理,但同時也說明成員變量在定義的時候初始化和在構造函數中賦值的意義是不一樣的,至少執行順序不一樣,產生的結果可能也不一樣。
作者:陳太漢