2013-08-01 48 views
16

我正在c#中製作軟件。我使用的是抽象類,Instruction,擁有這些代碼位:解決'構造函數中的虛擬方法調用'問題

protected Instruction(InstructionSet instructionSet, ExpressionElement newArgument, 
    bool newDoesUseArgument, int newDefaultArgument, int newCostInBytes, bool newDoesUseRealInstruction) { 

    //Some stuff 

    if (DoesUseRealInstruction) { 
     //The warning appears here. 
     RealInstruction = GetRealInstruction(instructionSet, Argument); 
    } 
} 

public virtual Instruction GetRealInstruction(InstructionSet instructionSet, ExpressionElement argument) { 
    throw new NotImplementedException("Real instruction not implemented. Instruction type: " + GetType()); 
} 

所以ReSharper的告訴我,在標記線I AM「調用虛方法構造」這是不好的。我瞭解構造函數被調用的順序。該GetRealInstruction方法的所有覆蓋這個樣子:

public override Instruction GetRealInstruction(InstructionSet instructionSet, ExpressionElement argument) { 
    return new GoInstruction(instructionSet, argument); 
} 

所以他們不依賴於任何同類數據;他們只是返回一些依賴派生類型的東西。 (所以構造函數的順序不會影響它們)。

那麼,我應該忽略它嗎?我寧願不;所以任何人都可以告訴我如何避免這種警告?

我不能使用委託整齊,因爲GetRealInstruction方法有一個更多的重載。

回答

16

我已經遇到過這個問題好幾次了,我發現正確解決它的最好方法是從構造函數中抽象被調用的虛方法到一個單獨的類中。然後,將這個新類的實例傳遞到原始抽象類的構造函數中,每個派生類都將其自己的版本傳遞給基類構造函數。這是一個有點棘手的解釋,所以我會舉一個例子,基於你的。

public abstract class Instruction 
{ 
    protected Instruction(InstructionSet instructionSet, ExpressionElement argument, RealInstructionGetter realInstructionGetter) 
    { 
     if (realInstructionGetter != null) 
     { 
      RealInstruction = realInstructionGetter.GetRealInstruction(instructionSet, argument); 
     } 
    } 

    public Instruction RealInstruction { get; set; } 

    // Abstracted what used to be the virtual method, into it's own class that itself can be inherited from. 
    // When doing this I often make them inner/nested classes as they're not usually relevant to any other classes. 
    // There's nothing stopping you from making this a standalone class of it's own though. 
    protected abstract class RealInstructionGetter 
    { 
     public abstract Instruction GetRealInstruction(InstructionSet instructionSet, ExpressionElement argument); 
    } 
} 

// A sample derived Instruction class 
public class FooInstruction : Instruction 
{ 
    // Passes a concrete instance of a RealInstructorGetter class 
    public FooInstruction(InstructionSet instructionSet, ExpressionElement argument) 
     : base(instructionSet, argument, new FooInstructionGetter()) 
    { 
    } 

    // Inherits from the nested base class we created above. 
    private class FooInstructionGetter : RealInstructionGetter 
    { 
     public override Instruction GetRealInstruction(InstructionSet instructionSet, ExpressionElement argument) 
     { 
      // Returns a specific real instruction 
      return new FooRealInstuction(instructionSet, argument); 
     } 
    } 
} 

// Another sample derived Instruction classs showing how you effictively "override" the RealInstruction that is passed to the base class. 
public class BarInstruction : Instruction 
{ 
    public BarInstruction(InstructionSet instructionSet, ExpressionElement argument) 
     : base(instructionSet, argument, new BarInstructionGetter()) 
    { 
    } 

    private class BarInstructionGetter : RealInstructionGetter 
    { 
     public override Instruction GetRealInstruction(InstructionSet instructionSet, ExpressionElement argument) 
     { 
      // We return a different real instruction this time. 
      return new BarRealInstuction(instructionSet, argument); 
     } 
    } 
} 

在您的具體的例子它變得有點混亂,我就開始跑了明智的名字,但是這是由於這一事實,你已經有了說明中說明的嵌套,即指令有RealInstruction (或至少可選地);但正如你所看到的,它仍然有可能實現你想要的,並避免來自構造函數的任何虛擬成員調用。

如果還不清楚,我還會舉一個例子,這個例子是我最近在自己的代碼中使用的。在這種情況下,我有兩種類型的表單,一個表頭和一個消息表單,它們都是從一個基本表單繼承而來的。所有表單都有字段,但每個表單類型都有不同的機制來構造它的字段,所以我最初有一個名爲GetOrderedFields的抽象方法,我從基礎構造函數中調用,並且方法在每個派生表單類中被重寫。這給了我你提到的resharper警告。我的解決辦法是相同的模式上方,如下

internal abstract class FormInfo 
{ 
    private readonly TmwFormFieldInfo[] _orderedFields; 

    protected FormInfo(OrderedFieldReader fieldReader) 
    { 
     _orderedFields = fieldReader.GetOrderedFields(formType); 
    } 

    protected abstract class OrderedFieldReader 
    { 
     public abstract TmwFormFieldInfo[] GetOrderedFields(Type formType); 
    } 
} 

internal sealed class HeaderFormInfo : FormInfo 
{ 
    public HeaderFormInfo() 
     : base(new OrderedHeaderFieldReader()) 
    { 
    } 

    private sealed class OrderedHeaderFieldReader : OrderedFieldReader 
    { 
     public override TmwFormFieldInfo[] GetOrderedFields(Type formType) 
     { 
      // Return the header fields 
     } 
    } 
} 

internal class MessageFormInfo : FormInfo 
{ 
    public MessageFormInfo() 
     : base(new OrderedMessageFieldReader()) 
    { 
    } 

    private sealed class OrderedMessageFieldReader : OrderedFieldReader 
    { 
     public override TmwFormFieldInfo[] GetOrderedFields(Type formType) 
     { 
      // Return the message fields 
     } 
    } 
} 
+0

我忘了提及,但這種方式的另一個好處是,在你的情況下,你避免了基本方法虛擬的需要,而不是抽象的,因此你得到編譯時檢查,而不是拋出一個異常(@TarasDzyoba提到) 。 – Richard

+0

這是非常深思熟慮,謝謝。我最終以另一種方式避免了這個問題,但這是未來非常好的解決方案。 –

1

你可以通過在現實指令到基類的構造函數:

protected Instruction(..., Instruction realInstruction) 
{ 
    //Some stuff 

    if (DoesUseRealInstruction) { 
     RealInstruction = realInstruction; 
    } 
} 

public DerivedInstruction(...) 
    : base(..., GetRealInstruction(...)) 
{ 
} 

或者,如果你真的想叫您構造一個虛擬函數(我強烈discorage你),你可以抑制ReSharper的警告:

// ReSharper disable DoNotCallOverridableMethodsInConstructor 
    RealInstruction = GetRealInstruction(instructionSet, Argument); 
// ReSharper restore DoNotCallOverridableMethodsInConstructor 
+0

這將工作得很好,但它不會消除警告的原因,只是隱藏它。如果沒有其他解決方案,我可以使用它。我喜歡一次工作,並設置自己的事情。我不喜歡給設置給調用者。 –

+0

答覆已修改。但我想這還不是你要找的。我懷疑是否有這樣的解決方案。 ReSharper警告有[很好的理由](http://stackoverflow.com/questions/119506/virtual-member-call-in-a-constructor?rq=1)。 –

+0

我明白了。我最終將'GetRealInstruction'調用移至另一個函數,以便在需要時調用它。 –

2

當你創建你的派生類的實例,調用棧看起來就像這樣:

GetRealInstruction() 
BaseContructor() 
DerivedConstructor() 

GetRealInstruction在派生類中被覆蓋,派生類的構造函數尚未完成運行。

我不知道你的其他代碼是怎麼樣的,但你應該首先檢查你是否真的需要一個成員變量。你有一個方法返回你需要的對象。如果你真的需要它,建立一個屬性並在獲取者中調用GetRealInstruction()

也可以製作GetRealInstruction摘要。這樣你不必拋出異常,如果忘記在派生類中重寫它,編譯器會給你一個錯誤。

+0

我瞭解調用訂單問題。我只需要某些派生類中的GetRealInstruction(那些設置了doesRequireRealInstruction位的類),所以我不應該把它抽象化。我並不想爲此創建一個屬性,因爲我喜歡字段和方法之間的訪問/操作差異。 (我在這種情況下使用屬性,但對於更簡單的操作),我最終將呼叫轉移到其他地方。 –

2

可以引入另一個抽象類RealInstructionBase所以你的代碼如下:

public abstract class Instruction { 
    public Instruction() { 
     // do common stuff 
    } 
} 

public abstract class RealInstructionBase : Instruction { 
    public RealInstructionBase() : base() { 
     GetRealInstruction(); 
    } 

    protected abstract object GetRealInstruction(); 
} 

現在每個需要使用RealInstruction從RealInstructionBase派生和所有其他來自指令獲得指令。這樣你應該讓它們都正確初始化。

編輯:好吧,這隻會給你一個更清潔的設計(如果在構造函數中沒有),但沒有擺脫警告。 現在,如果您想知道爲什麼首先得到警告,可以參考this question。基本上重要的一點是,當你將你的類標記爲密封的抽象方法時,你將會很安全。

+0

這是無可厚非的。然而,我需要在需要之前移動「GetRealInstruction」調用,而不是在構造函數中進行排序。這個功能不是太昂貴。 –

0

另一種選擇是引入Initialize()方法,你做的一切,需要一個完全構造的對象初始化。

相關問題