解决’构造函数中的虚方法调用’问题

我正在用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告诉我,在标记的行中,我“在构造函数中调用虚方法”并且这很糟糕。 我理解构造函数被调用的顺序。 GetRealInstruction方法的所有覆盖内容如下所示:

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

所以他们不依赖于class上的任何数据; 他们只返回依赖于派生类型的东西。 (因此构造函数顺序不会影响它们)。

那么,我应该忽略它吗? 我宁愿不; 所以任何人都可以告诉我如何避免这种警告?

我不能巧妙地使用委托,因为GetRealInstruction方法还有一个重载。

我已经遇到过这个问题很多次了,我发现正确解决它的最好方法是将从构造函数调用的虚方法抽象到一个单独的类中。 然后,您将此新类的实例传递到原始抽象类的构造函数中,每个派生类将其自己的版本传递给基础构造函数。 解释起来有点棘手,所以我会根据你的例子给出一个例子。

 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(或至少可选) ; 但正如您所看到的那样仍然可以实现您想要的并避免来自构造函数的任何虚拟成员调用。

如果仍然不清楚,我还将根据我最近在自己的代码中使用的示例给出一个示例。 在这种情况下,我有两种类型的表单,标题表单和消息表单,两者都从基本表单inheritance。 所有表单都有字段,但每个表单类型都有不同的机制来构造它的字段,所以我最初有一个名为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 } } } 

当您创建派生类的实例时,您的调用堆栈将如下所示:

 GetRealInstruction() BaseContructor() DerivedConstructor() 

在派生类中重写了GetRealInstruction ,其构造函数尚未完成运行。

我不知道你的其他代码看起来如何,但在这种情况下你应该首先检查你是否真的需要一个成员变量。 您有一个返回所需对象的方法。 如果你确实需要它,请创建一个属性并在getter中调用GetRealInstruction()

您也可以将GetRealInstruction抽象化。 这样你就不必抛出exception,如果忘记在派生类中覆盖它,编译器会给你一个错误。

您可以引入另一个抽象类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,而所有其他指令都来自于Instruction。 这样你就可以正确初始化它们。

编辑 :好的,这只会给你一个更清洁的设计(如果在构造函数中没有)但没有摆脱警告。 现在,如果你想知道为什么你首先得到警告,你可以参考这个问题 。 基本上,关键在于,当您将实现抽象方法的类标记为密封时,您将是安全的。

您可以将实际指令传递给基类构造函数:

 protected Instruction(..., Instruction realInstruction) { //Some stuff if (DoesUseRealInstruction) { RealInstruction = realInstruction; } } public DerivedInstruction(...) : base(..., GetRealInstruction(...)) { } 

或者,如果你真的想从你的构造函数中调用一个虚函数(我强烈劝阻你),你可以抑制ReSharper警告:

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

另一个选择是引入Initialize()方法,在该方法中执行需要完全构造的对象的所有初始化。