2013-08-01 47 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()方法,你做的一切,需要一个完全构造的对象初始化。

相关问题