2012-01-25 57 views
3

在子類中,是否有方法同時調用公共參數化構造函數以及protected/private構造函數,同時仍然調用基類的構造函數?構造子順序在子類

例如,假設下面的代碼:

using System; 

public class Test 
{ 
    void Main() 
    { 
    var b = new B(1); 
    } 

    class A 
    { 
    protected A() 
    { 
     Console.WriteLine("A()"); 
    } 


    public A(int val) 
     : this() 
    { 
     Console.WriteLine("A({0})", val); 
    } 

    } 

    class B : A 
    { 
    protected B() 
    { 
     Console.WriteLine("B()"); 
    } 

    public B(int val) 
     : base(val) 
    { 
     Console.WriteLine("B({0})", val); 
    } 

    } 
} 

給出的輸出是:

A() 
A(1) 
B(1) 

然而,這是我所期待的:

A() 
B() 
A(1) 
B(1) 

是否有通過構造函數鏈實現這一點?或者我應該在A中有一個OnInitialize()類型方法,它是抽象的或虛擬的,在B中被覆蓋,並從A的受保護的無參數構造函數調用?

+1

你不會調用'B()',那麼你爲什麼期望在輸出中看到? –

+0

我希望或者希望有一種方法可以將'B()'標記爲'A()'的重寫。我不能從'B(int)'調用'B()',因爲'A'中的構造函數都不會被調用。 – Hugo

+0

這是不正確的。在這種情況下,A的默認構造函數將運行。 –

回答

1

不,您正在尋找的東西不可能只使用構造函數。你基本上要求構造函數鏈「分支」,這是不可能的;每個構造函數可以在當前類或父類中調用一個(也是唯一的)構造函數。

通過使用您在基類中調用的虛擬初始化函數,完成此操作的唯一方法是(如您所建議的那樣)。通常情況下,從構造函數中調用虛擬成員是不受歡迎的,因爲您可以讓子類中的代碼在其構造函數之前執行,但考慮到這正是您所追求的內容,那是您唯一真正的方法。

5

你的評論有兩個誤解。

首先,派生類中的構造函數不會在基類中覆蓋覆蓋構造函數。相反,他們給他們打電話。 覆蓋一種方法意味着它被稱爲而不是一個基類的等價方法,不如。

其次,總是有一個叫基礎構造函數。任何沒有明確調用基礎構造函數的構造函數都會隱式調用無參數基礎構造函數。

您可以通過刪除A()構造函數或將其設爲私有來演示此操作。不調用基類構造函數的B()構造函數現在將成爲編譯器錯誤,因爲沒有可用於隱式調用的無參數構造函數。

由於BA衍生B一個A。因此B不能成功構建沒有A正在構建(如建造一輛跑車沒有建立一輛車)。

施工發生在基地優先。 A的構造函數負責確保A的實例完全構造並處於有效狀態。這是B的構造函數將B的實例置於有效狀態的起點。

至於的想法:

或者我應該有一個OnInitialize()中鍵入方法A是其在乙重寫並從受保護的,無參數的構造或者叫做抽象或虛擬一個?

絕對不是。在調用A的構造函數時,B的其餘部分尚未構造。然而,初始化者已經運行。這導致真正討厭的情況,如:

public abstract class A 
{ 
    private int _theVitalValue; 
    public A() 
    { 
    _theVitalValue = TheValueDecider(); 
    } 
    protected abstract int TheValueDecider(); 
    public int TheImportantValue 
    { 
    get { return _theVitalValue; } 
    } 
} 
public class B : A 
{ 
    private readonly int _theValueMemoiser; 
    public B(int val) 
    { 
    _theValueMemoiser = val; 
    } 
    protected override int TheValueDecider() 
    { 
    return _theValueMemoiser; 
    } 
} 
void Main() 
{ 
    B b = new B(93); 
    Console.WriteLine(b.TheImportantValue); // Writes "0"! 
} 

在當A調用B覆蓋虛擬方法時,B的initialisers已經運行,但不是它的構造函數的其餘部分。因此虛擬方法正在B上運行,該狀態不是有效的狀態。

確實有可能使這種方法有效,但它也很容易導致很多痛苦。

相反:

定義你的類,使他們每個總是留下它們的構造在一個完全初始化和有效狀態。正是在這一點上,你爲這個類設置了不變量(一個不變量是一組必須始終保持真實的條件,在任何時候都可以從外部觀察一個類。例如,對於內部持有月日的日期類來說是可以的在單獨的字段中的值暫時處於2月31日的狀態,但是它不能在構建結束時或者在方法的開始或結束時處於這種狀態,因爲調用代碼會期望它是日期)。

抽象類可以依賴派生類來完成它的rôle,但不是它的初始狀態。如果一個設計正在朝着這個方向發展,那麼將這個狀態的責任進一步降低到惡作劇。例如,考慮:

public abstract class User 
{ 
    private bool _hasFullAccess; 
    protected User() 
    { 
    _hasFullAccess = CanSeeOtherUsersItems && CanEdit; 
    } 
    public bool HasFullAccess 
    { 
    get { return _hasFullAccess; } 
    } 
    protected abstract bool CanSeeOtherUsersItems {get;} 
    protected abstract bool CanEdit {get;} 
} 
public class Admin : User 
{ 
    protected override bool CanSeeOtherUsersItems 
    { 
    get { return true; } 
    } 
    protected override bool CanEdit 
    { 
    get { return true; } 
    } 
} 
public class Auditor : User 
{ 
    protected override bool CanSeeOtherUsersItems 
    { 
    get { return true; } 
    } 
    protected override bool CanEdit 
    { 
    get { return false; } 
    } 
} 

這裏,我們根據信息在派生類設置基類的狀態,並在此狀態下完成的任務。這將在這裏起作用,但如果屬性依賴於派生類中的狀態(這對於創建新的派生類來承擔某些事情被允許是完全合理的),則不會這樣。取而代之的是,我們做的功能,但不是國家依賴於派生類通過改變User到:

public abstract class User 
{ 
    public bool HasFullAccess 
    { 
    get { return CanSeeOtherUsersItems && CanEdit; } 
    } 
    protected abstract bool CanSeeOtherUsersItems {get;} 
    protected abstract bool CanEdit {get;} 
} 

(如果我們預計此類電話要貴有時,我們仍然可以在第一次通話memoise的結果,因爲這隻能在完全構造的User中發生,並且在回憶錄之前和之後的狀態都是有效且準確的狀態,以便User處於)。

定義了一個基類以使其構造函數保持有效狀態後,我們對派生類也做同樣的事情。它可以依賴於它的基類在它的構造函數中的狀態,因爲它的基類是完全構造的,並且它正在被構建的第1步。它在初始階段不能依賴於這個,儘管它不會像人們想要嘗試的東西那樣出現。

要返回的預期輸出:

A() 
B() 
A(1) 
B(1) 

這意味着你想把東西放在一個有效的起始狀態,然後進入不同的有效起始狀態。這沒有任何意義。

現在,我們當然可以使用this()表單從構造函數中調用構造函數,但應該將其純粹視爲便於保存輸入。它的意思是「這個構造函數完成所有被調用的功能,然後是一些」,除此之外別無其他。對外界來說,只有一個構造函數曾經被調用過。在沒有這種便利的語言,我們要麼重複碼或做:

private void repeatedSetupCode(int someVal, string someOtherVal) 
{ 
    someMember = someVal; 
    someOtherMember = someOtherVal; 
} 
public A(int someVal, string someOtherVal) 
{ 
    repeatedSetupCode(someVal, someOtherVal); 
} 
public A(int someVal) 
{ 
    repeatedSetupCode(someVal, null); 
} 
public A() 
{ 
    repeatedSetupCode(0, null); 
} 

這是很好的,我們有this()。它不會節省太多的輸入,但它確實表明所討論的內容屬於對象生命週期的構建階段。但如果我們真的需要,我們可以使用上面的表格,甚至可以保護repeatedSetupCode

雖然我會非常謹慎。以任何方式處理對方構造的對象,除了根據基類的不變性調用將基類放入有效狀態所需的單個基構造函數之外,是一種難聞的氣味,表明完全不同的方法可能會更好。

+0

有一個單獨的方法做初始化的一個缺點(就像你的''''''''''''''''''''''''''''''是你不能從那裏初始化'只讀'字段。這不是什麼大不了的事情,但知道這件事很好。 – svick

+1

@svick我會說這是一箇中等規模的交易,也許:)如果真的必須這樣做,人們可以使用它,但是,只有當您要將其展示給另一個班級,並且干涉另一個班級的構建除了將值發送到基地或具有可由派生讀取的設置屬性之外,是一種難聞的氣味。 –

+0

+1爲一個很好的總結! –