Ответ 1
Я столкнулся с этой проблемой довольно много раз, и лучшим способом, который я нашел, чтобы правильно ее решить, является абстрагирование виртуального метода, вызываемого из конструктора, в отдельный класс. Затем вы передадите экземпляр этого нового класса в конструктор исходного абстрактного класса, причем каждый производный класс передает его собственную версию базовому конструктору. Это немного сложно объяснить, поэтому я приведу пример, основанный на вашем.
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 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 nothing stopping you from making this a standalone class of it 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 (или, по крайней мере, необязательно); но, как вы видите, все еще возможно достичь того, чего вы хотите, и избежать любых вызовов виртуальных членов от конструктора.
В случае, если это еще не ясно, я также приведу пример, основанный на том, что я использовал совсем недавно в своем собственном коде. В этом случае у меня есть 2 типа форм, форма заголовка и форма сообщения, каждая из которых наследуется от базовой формы. Все формы имеют поля, но каждый тип формы имеет другой механизм для построения своих полей, поэтому изначально у меня был абстрактный метод, называемый 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
}
}
}