Решение проблемы "Виртуальный вызов метода в конструкторе"
Я делаю программное обеспечение в 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());
}
Итак, Решарпер говорит мне, что в отмеченной строке я "вызываю виртуальный метод в конструкторе", и это плохо. Я понимаю, что такое порядок вызова конструкторов. Все переопределения GetRealInstruction
метод выглядит так:
public override Instruction GetRealInstruction(InstructionSet instructionSet, ExpressionElement argument) {
return new GoInstruction(instructionSet, argument);
}
Таким образом, они не зависят от каких-либо данных в классе; они просто возвращают то, что зависит от производного типа. (поэтому порядок конструктора не влияет на них).
Итак, я должен игнорировать это? Я бы не; так кто-нибудь может показать мне, как я могу избежать этого предупреждения?
Я не могу использовать делегатов аккуратно, потому что GetRealInstruction
У метода есть еще одна перегрузка.
5 ответов
Я сталкивался с этой проблемой довольно много раз, и лучший способ найти правильное решение - это абстрагировать виртуальный метод, вызываемый из конструктора, в отдельный класс. Затем вы должны передать экземпляр этого нового класса в конструктор вашего исходного абстрактного класса, а каждый производный класс передает свою собственную версию базовому конструктору. Это немного сложно объяснить, поэтому я приведу пример, основанный на вашем.
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 (или, по крайней мере, при желании).; но, как вы можете видеть, все еще возможно достичь того, чего вы хотите, и избежать любых виртуальных вызовов членов из конструктора.
В случае, если это все еще не ясно, я также приведу пример, основанный на примере, который я недавно использовал в своем собственном коде. В этом случае у меня есть 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
}
}
}
Вы можете ввести другой абстрактный класс 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. Таким образом, вы должны правильно инициализировать их.
РЕДАКТИРОВАТЬ: Хорошо, это только даст вам более чистый дизайн (нет, если в конструкторе), но не избавится от предупреждения. Теперь, если вы хотите знать, почему вы получаете предупреждение, вы можете обратиться к этому вопросу. По сути, дело в том, что вы будете в безопасности, когда отметите свои классы, где вы реализуете абстрактный метод, как закрытые.
Когда вы создаете экземпляр вашего производного класса, ваш стек вызовов будет выглядеть так:
GetRealInstruction()
BaseContructor()
DerivedConstructor()
GetRealInstruction
переопределяется в производном классе, конструктор которого еще не завершил работу.
Я не знаю, как выглядит ваш другой код, но вы должны сначала проверить, действительно ли вам нужна переменная-член в этом случае. У вас есть метод, который возвращает нужный вам объект. Если вам это действительно нужно, сделайте недвижимость и позвоните GetRealInstruction()
в добытчике.
Также вы можете сделать 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
Другим вариантом является введение Initialize()
метод, где вы делаете всю инициализацию, которая требует полностью построенного объекта.