2011-08-18 76 views
5

構造函數按從上到下的順序執行I.E.基地的第一個,然後派生一個。這種安排基於一個重要的OOP保證,即一個對象(在這裏是基礎)必須在它可以被使用之前被初始化(這裏是派生類的構造函數)。爲什麼派生類的字段初始值設定項在基類的初始化符之前執行

我想知道爲什麼字段初始值設定項在C#中不遵循這個原則?我在這裏錯過了什麼嗎?

我也遇到了這個原理與字段初始值設定項的用處。我有一個返回Identity對象的屬性的基類。每個派生類都有自己的存儲庫字段,我已經使用字段初始值設定項(使用默認構造函數)進行了初始化。最近我已經決定存儲庫類也必須提供Identity對象,所以我在存儲庫構造函數中引入了一個額外的參數。但我堅持要找出:

public class ForumController : AppControllerBase 
{ 
     ForumRepository repository = new ForumRepository(Identity); 
    // Above won't compile since Identity is in the base class. 

    // ... Action methods. 
} 

現在我只剩一個選擇那就是我的每一個控制器,一個默認的構造豐滿只能做身份庫對象的初始化的工作。

+0

不,它沒有編譯的原因根本就不在於'Identity'在基類中,只是因爲它是一個實例成員。所以,標題中的問題與你想要做的事無關...... – Guffa

回答

0

字段初始值在構造函數中執行,並且由於首先調用基類中的構造函數,所有字段初始值也在派生構造函數執行之前執行。

實施例:

public class Base { 

    // field initialiser: 
    private string _firstName = "Arthur"; 

    public string FirstName { get { return _firstName;}} 
    public string LastName { get; private set; } 

    // initialiser in base constructor:  
    public Base() { 
    LastName = "Dent"; 
    } 

} 

public class Derived : Base { 

    public string FirstNameCopy { get; private set; } 
    public string LastNameCopy { get; private set; } 

    public Derived() { 
    // get values from base class: 
    FirstNameCopy = FirstName; 
    LastNameCopy = LastName; 
    } 

} 

測試:

Derived x = new Derived(); 
Console.WriteLine(x.FirstNameCopy); 
Console.WriteLine(x.LastNameCopy); 

輸出:

Arthur 
Dent 
+0

我想你沒有明白我的觀點。我已經知道你在說什麼了。我的問題是給定基類和派生類中的field-initializers,爲什麼派生類首先被執行,然後是基類?如果您看到構造函數,則完全相反。 –

+0

@Varun K:我明白了,您正試圖從字段初始化程序訪問字段。你不能那樣做,因爲你不能使用來自初始化者的實例成員,而這與不同類中的字段無關。 – Guffa

+0

我知道我不能那樣做:-)。但我想知道背後的原因。 –

0

字段初始化並不意味着對構造的替代品。

根據MSDN文檔,所有字段初始值設定項在構造函數之前執行。然而,字段初始值設定項的限制是它們不能引用其他實例字段。 (http://msdn.microsoft.com/en-us/library/ms173118(v=vs.80).aspx

此限制是由於無法確定正確的順序以及在編譯器級別執行字段初始值設定項的依賴性。

你將不得不寫一個默認的構造函數來實現你想要的。然而,你可以嘗試使用靜態字段。這是一個不好的做法,甚至可能不適合你的設計。


編輯澄清問題的評論問:

至於派生類而言,基類領域看起來是一樣的,他們是否是通過初始化或構造函數初始化。這些信息不能提供給子類,因爲這意味着暴露基類的實現(這對基類是嚴格私有的)。

這意味着派生類的初始化程序無法確定在訪問它們之前是否所有基類字段都已初始化。因此,行爲是不允許的。

+0

我已經知道這個限制點(正確的順序和依賴),但如果你看到,這是嚴格適用於同一類中的字段。我正在考慮字段 - 派生類中的基本初始值設定項或初始值設定項 –

2

在C#中,一個構造函數運行順序:

 
    Perform all field initializers 
    Chain to base class constructor 
    Execute user-supplied code for constructor 

在vb.net,順序爲:

 
    Chain to base class constructor 
    Perform all field initializers 
    Execute user-supplied code for constructor 

沒有框架基礎這就是爲什麼C#按照它所做的順序執行操作的原因,vb.net可以以不同的順序執行它們這一事實就是證明。 C#方法的設計理由是,在所有字段初始值設定項(對於派生類以及基類字段)都已運行之前,不應該有對象暴露於外部世界的可能性;因爲基類構造函數可以將對象暴露給外部世界,所以強制執行該要求意味着字段初始化符必須在基類構造函數之前運行。

就我個人而言,我並不覺得這種說法特別令人信服。有很多情況下,如果沒有在基礎構造函數運行之前不會提供的信息,將無法將字段設置爲有用值。任何基本構造函數都可能暴露部分構造實例的代碼都需要爲這種可能性做好準備。雖然有時候指定字段初始值設定項應該「儘早」運行是有用的,但我認爲還有很多情況會讓他們能夠訪問該fledling對象(在某種程度上,因爲我相信在類實例的生命週期中值類型應該被視爲不變量的類字段應該在實際中通過初始化程序而不是命令性地在構造函數中聲明式設置)。

順便提一下,我希望在vb.net和C#中看到的一個特性將是一種聲明參數初始化字段和僞字段的方法。如果一個類具有某個特定名稱和類型的參數初始化字段,那麼該類的每個構造函數都不會鏈接到另一個相同的類必須包含具有適當名稱和類型的參數。這些字段的值將在其他任何事情完成之前在構造函數中設置,並且可以被其他字段初始值設定程序訪問。僞字段在語法上的行爲與字段類似,只不過它們會在字段初始值設定項內使用,只有可用,並且會在構造函數中作爲局部變量實現。這樣的特徵會使得許多類型的結構更方便。例如,如果一個類型應該保持一個特定的陣列實例在其整個生命週期,能夠說:

 
    readonly param int Length; 
    readonly ThingType[] myArray = new ThingType[Length]; 

將不必無論是構建在類的構造排列似乎更好(這可能不會發生直到基礎構造函數運行之後)或(對於vb.net)必須將該長度傳遞給基類構造函數,然後可以使用該基類構造函數來設置字段(然後該字段將佔用類中的空間,即使其值 - 如上面的Length - 可能是多餘的)。