public class BaseA{ public static MyTest a1 = new MyTest("a1"); public MyTest a2 = new MyTest("a2"); static BaseA() { MyTest a3 = new MyTest("a3"); } public BaseA() { MyTest a4 = new MyTest("a4"); } public virtual void MyFun() { MyTest a5 = new MyTest("a5"); }}public class BaseB : BaseA{ public static MyTest b1 = new MyTest("b1"); public MyTest b2 = new MyTest("b2"); static BaseB() { MyTest b3 = new MyTest("b3"); } public BaseB() { MyTest b4 = new MyTest("b4"); } public new void MyFun() { MyTest b5 = new MyTest("b5"); }}static class Program{ static void Main() { BaseB baseb = new BaseB(); baseb.MyFun(); }}public class MyTest{ public MyTest(string info) { Console.WriteLine(info); }}
最後的問題是:請寫出Main()方法中,a1-a5,b1-b5這十個類執行個體化的順序。(MyTest類是我自己添的,方便查看結果,原題是是執行個體化一個object類。)
不知道園子裡有多少人能胸有成竹的寫出正確答案,反正我是答錯了,正確答案是:
b1
b3
b2
a1
a3
a2
a4
b4
b5
題目中涉及到的知識點
雖然題目沒做對了,但要知道自己為什麼會做錯,這樣才會有所提高,趁著端午的假期,我把這個面試題涉及到的知識點都梳理了一遍,要點如下:
- 內聯(inline)方式初始化欄位。
- 類型構造器(靜態建構函式)的執行時間。
- C#中基類和子類執行個體化的順序。
- new修飾符的作用。
內聯方式初始化欄位
這個知識點在《CLR via C#》書中有講到,所謂內聯方式,就是初始化欄位的一種簡化文法。來看範例程式碼:
public class SomeType{ public int m_x = 5;}
這種在類中聲明變數時進行賦值的方式就叫做內聯,大致等效於下面的代碼:
public class SomeType{ public int m_x; public SomeType() { m_x = 5; }}
之所以說“大致等效”,因為兩者的執行順序上略有差異,編譯器會首先產生內聯方式的代碼,然後再調用建構函式。
比如,下面的代碼,最後m_x的結果就為10。
public class SomeType{ //先執行 public int m_x=5; public SomeType() { //後執行 m_x = 10; }}類型構造器的執行
所謂類型構造器也就是我們熟知的靜態構造方法,在我們編寫的類中,都會有一個預設的靜態無參構造方法,跟無參執行個體構造方法一樣是預設存在的。
每當我們對一個類建立第一個執行個體或訪問靜態欄位前,JIT編譯器就會調用該類的靜態構造方法。當然,靜態變數也可以使用上面說的內聯方法進行賦值。
這裡可以看出,當第一次執行個體化某個類時,會首先調用該類的靜態構造方法。
C#中基類和子類執行個體化的順序
這個知識點比較簡單,那就是在調用子類執行個體構造方法之前會調用基類的執行個體構造方法。從面試題的結果可以看出,基類的構造方法又比子類的靜態建構函式晚一些,此處因個人能力有限,我也沒辦法從更底層的角度去分析原理,只能暫且記住吧。
new修飾符的作用
我看過不少關於new以修飾符的形式用在方法聲明中的題目,關於new的用法在MSDN上也都查的到,官方說法是“顯式隱藏從基類繼承的成員”。
我個人的理解比較簡單:當子類中,一個方法的簽名(指參數,方法名,傳回值)與基類的一個方法相同,通過加入new修飾符,可以讓子類不做更改的去使用該方法。
說到底,new修飾符就是讓兩個不相關的同名方法同時存在而已。(這裡同名指相同的方法簽名)
原文:http://www.cnblogs.com/hkncd/archive/2011/06/05/2073404.html
幾個原則:
1.類的靜態成員是在執行個體成員前被初始化的,涉及到類的載入和主動使用。
2.內聯寫法的初始化是最先的,無論是對於靜態還是執行個體成員。
3.基類建構函式是在子類建構函式前調用的。
還有就是顯式類型建構函式的影響。顯示類型構造器可能包含具有副作用的代碼,所以要精確拿捏調用靜態建構函式的時間。精確也就是JIT編譯器剛好在調用之前插入調用指令。由於BaseA的建構函式是在BaseB的建構函式調用的時候才被調用,所以調用類型構造器的指令就被插入這裡了。
由於執行個體欄位的內聯寫法是在調用父類構造器之前,所以b2會在a1之前。