Résolution du problème ‘Appel de méthode virtuelle dans le constructeur’

Je fabrique un logiciel en c #. J’utilise une classe abstraite, Instruction , qui contient ces bits de code:

 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); } } 

et

 public virtual Instruction GetRealInstruction(InstructionSet instructionSet, ExpressionElement argument) { throw new NotImplementedException("Real instruction not implemented. Instruction type: " + GetType()); } 

Ainsi, Resharper me dit qu’à la ligne marquée, j’appelle une méthode virtuelle dans le constructeur et que c’est mauvais. Je comprends la chose à propos de l’ordre dans lequel les constructeurs sont appelés. Tous les remplacements de la méthode GetRealInstruction ressemblent à ceci:

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

Ils ne dépendent donc pas des données de la classe. ils renvoient simplement quelque chose qui dépend du type dérivé. (afin que l’ordre du constructeur ne les affecte pas).

Alors, devrais-je l’ignorer? Je ne préfère pas; alors quelqu’un pourrait-il me montrer comment éviter cet avertissement?

Je ne peux pas utiliser les delegates proprement parce que la méthode GetRealInstruction a une surcharge supplémentaire.

J’ai rencontré ce problème plusieurs fois et le meilleur moyen que j’ai trouvé de le résoudre correctement consiste à abstraire la méthode virtuelle appelée par le constructeur dans une classe séparée. Vous transmettez ensuite une instance de cette nouvelle classe au constructeur de votre classe abstraite d’origine, chaque classe dérivée transmettant sa propre version au constructeur de base. C’est un peu difficile à expliquer, alors je vais vous donner un exemple, basé sur le vôtre.

 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); } } } 

Dans votre exemple particulier, cela devient un peu déroutant et j’ai commencé à manquer de noms raisonnables, mais cela est dû au fait que vous avez déjà une imbrication d’instructions dans des instructions, c’est-à-dire qu’une instruction a une RealInstruction (ou du moins, le cas échéant). ; mais comme vous pouvez le constater, il est toujours possible de réaliser ce que vous voulez et d’éviter les appels de membres virtuels depuis un constructeur.

Au cas où ce ne serait toujours pas clair, je donnerai également un exemple basé sur celui que j’ai utilisé récemment dans mon propre code. Dans ce cas, j’ai 2 types de formulaires, un formulaire en-tête et un formulaire de message, qui héritent tous deux d’un formulaire de base. Tous les formulaires ont des champs mais chaque type de formulaire a un mécanisme différent pour la construction de ses champs. J’ai donc initialement eu une méthode abstraite appelée GetOrderedFields que j’ai appelée à partir du constructeur de base et la méthode a été remplacée dans chaque classe de formulaire dérivée. Cela m’a donné l’avertissement resharper vous mentionnez. Ma solution était la même chose que ci-dessus et est comme suit

 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 } } } 

Lorsque vous créez une instance de votre classe dérivée, votre stack d’appels ressemblera à ceci:

 GetRealInstruction() BaseContructor() DerivedConstructor() 

GetRealInstruction est substitué dans la classe dérivée, dont le constructeur n’a pas encore fini de s’exécuter.

Je ne sais pas à quoi ressemble votre autre code, mais vous devez d’abord vérifier si vous aviez réellement besoin d’une variable membre dans ce cas. Vous avez une méthode qui renvoie l’object dont vous avez besoin. Si vous en avez vraiment besoin, créez une propriété et appelez GetRealInstruction() dans le getter.

Aussi, vous pouvez faire GetRealInstruction résumé de GetRealInstruction . De cette façon, vous n’avez pas à lancer l’exception et le compilateur vous donnera une erreur si vous oubliez de la remplacer dans une classe dérivée.

Vous pouvez introduire une autre classe abstraite RealInstructionBase afin que votre code ressemble à ceci:

 public abstract class Instruction { public Instruction() { // do common stuff } } public abstract class RealInstructionBase : Instruction { public RealInstructionBase() : base() { GetRealInstruction(); } protected abstract object GetRealInstruction(); } 

Désormais, chaque instruction devant utiliser RealInstruction dérive de RealInstructionBase et toutes les autres dérivent de Instruction. De cette façon, vous devriez les avoir tous correctement initialisés.

EDIT : Ok, cela ne vous donnera qu’un design plus propre (non si dans le constructeur) mais ne vous débarrasse pas de l’avertissement. Maintenant, si vous souhaitez savoir pourquoi vous recevez cet avertissement, vous pouvez vous référer à cette question . En gros, le fait est que vous serez en sécurité lorsque vous marquerez vos classes dans lesquelles vous implémentez la méthode abstraite comme étant scellée.

Vous pouvez transmettre l’instruction réelle au constructeur de la classe de base:

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

Ou, si vous voulez vraiment appeler une fonction virtuelle à partir de votre constructeur (ce dont je vous déconnecte fortement), vous pouvez supprimer l’avertissement ReSharper:

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

Une autre option consiste à introduire une méthode Initialize() , dans laquelle vous effectuez toute l’initialisation nécessitant un object entièrement construit.