C# 解决“构造函数中的虚拟方法调用”问题
我正在用c语言制作一个软件。我使用的是一个抽象类指令,它包含以下代码:C# 解决“构造函数中的虚拟方法调用”问题,c#,inheritance,constructor,resharper,C#,Inheritance,Constructor,Resharper,我正在用c语言制作一个软件。我使用的是一个抽象类指令,它包含以下代码: protected Instruction(InstructionSet instructionSet, ExpressionElement newArgument, bool newDoesUseArgument, int newDefaultArgument, int newCostInBytes, bool newDoesUseRealInstruction) { //Some stuff
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);
}
}
及
所以Resharper告诉我,在标记的行中,我正在“调用构造函数中的虚拟方法”,这很糟糕。我理解调用构造函数的顺序。GetRealInstruction方法的所有重写如下所示:
public override Instruction GetRealInstruction(InstructionSet instructionSet, ExpressionElement argument) {
return new GoInstruction(instructionSet, argument);
}
GetRealInstruction()
BaseContructor()
DerivedConstructor()
因此,它们不依赖于类中的任何数据;它们只返回依赖于派生类型的内容。所以构造器顺序不会影响它们
那么,我应该忽略它吗?我宁愿不要;有人能告诉我怎么才能避免这个警告吗
我无法灵活地使用委托,因为GetRealInstruction方法还有一个重载。您可以将实际指令传递到基类构造函数中:
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
创建派生类的实例时,调用堆栈将如下所示:
public override Instruction GetRealInstruction(InstructionSet instructionSet, ExpressionElement argument) {
return new GoInstruction(instructionSet, argument);
}
GetRealInstruction()
BaseContructor()
DerivedConstructor()
GetRealInstruction在派生类中被重写,其构造函数尚未完成运行
我不知道您的其他代码看起来如何,但您应该首先检查在这种情况下是否确实需要一个成员变量。您有一个返回所需对象的方法。如果您真的需要它,那么在getter中创建一个属性并调用GetRealInstruction
您还可以将GetRealInstruction抽象化。这样,您就不必抛出异常,如果您忘记在派生类中重写它,编译器将给您一个错误。您可以引入另一个抽象类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,而所有其他指令都来自RealInstruction。这样,您应该正确地初始化它们
编辑:好的,这只会给你们一个更干净的设计,若在构造函数中并没有,但并没有摆脱警告。
现在,如果您想知道为什么首先会收到警告,您可以参考。基本上,关键是当您将实现抽象方法的类标记为密封时,您将是安全的。我已经多次遇到这个问题,我发现正确解决这个问题的最好方法是将从构造函数调用的虚拟方法抽象到一个单独的类中。然后将这个新类的实例传递到原始抽象类的构造函数中,每个派生类将自己的版本传递给基构造函数。解释起来有点难,所以我会举一个例子,以你的为基础
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);
}
}
}
在您的特定示例中,它确实变得有点混乱,我开始用尽合理的名称,但这是因为您已经在指令中嵌套了指令,即指令具有realstruction,或者至少可以选择具有realstruction;但正如您所看到的,仍然可以实现您想要的,并避免来自构造函数的任何虚拟成员调用
如果还不清楚,我还将给出一个基于我最近在自己的代码中使用的示例。在本例中,我有两种类型的表单,头表单和消息表单,它们都继承自基本表单。所有表单都有字段,但每个表单类型都有不同的字段构造机制,因此我最初有一个名为GetOrderedFields的抽象方法,我从基本构造函数调用该方法,该方法在每个派生表单类中都被重写。这给了我你提到的再竖琴警告。我的解决方案与上面的模式相同,如下所示
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
}
}
}
另一种选择是引入Initialize方法,在该方法中,您可以执行所有需要完全构造的对象的初始化。这将很好地工作,但它不会删除警告的原因,只是将其隐藏。如果没有其他解决方案,我可以使用它。我喜欢工作一次,把事情安排好。我不喜欢给来电者设置。答案已修改。但我想这仍然不是你想要的。我怀疑是否有这样的解决办法。再竖琴的警告是没有用的,我明白。我最终将GetRealInstruction调用移动到另一个函数,以便在需要时调用它。我理解调用顺序问题。我只需要在某些派生类中使用GetRealInstruction,即设置了DoesRequireAllInstruction位的派生类,所以我不应该将其抽象化。我真的不想为此创建属性,因为我喜欢字段和方法之间的访问/操作差异。我确实在这样的情况下使用了属性,但对于简单得多的操作,我最终将调用转移到了其他地方。这显然更干净。然而,我设法在需要GetRealInstruction调用之前,而不是在构造函数中,移动GetRealInstruction调用来解决这个问题。这个功能不太贵。我
忘了提一下,但是这种方法的另一个好处是,在您的例子中,您不需要让基方法是虚拟的,而不是抽象的,因此您可以进行编译时检查,而不是像@TarasDzyoba提到的那样抛出异常。这是经过深思熟虑的,谢谢。我最终以另一种方式避免了这个问题,但这是未来一个非常好的解决方案。