2013-08-01 15 views
16

Ich mache eine Software in C#. Ich verwende eine abstrakte Klasse, Instruction, dass diese Bit-Code hat:Lösung der 'virtuellen Methodenaufruf im Konstruktor' Problem

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

und

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

So ReSharper mir sagt, dass ich an der markierten Linie bin ‚eine virtuelle Methode in Konstruktor aufrufen‘ und das ist schlecht. Ich verstehe die Sache über die Reihenfolge, in der die Konstruktoren aufgerufen werden. Alle Überschreibungen des GetRealInstruction Methode wie folgt aussehen:

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

Also nicht auf alle Daten in der Klasse, die sie hängen; Sie geben nur etwas zurück, das vom abgeleiteten Typ abhängt. (also betrifft die Reihenfolge der Konstruktoren sie nicht).

Also, sollte ich es ignorieren? Ich würde lieber nicht; Könnte mir jemand zeigen, wie ich diese Warnung vermeiden könnte?

Ich kann Delegaten nicht sauber verwenden, weil die GetRealInstruction Methode eine weitere Überlast hat.

Antwort

16

Ich habe dieses Problem schon einige Male festgestellt und der beste Weg, den ich gefunden habe, um es richtig aufzulösen, besteht darin, die virtuelle Methode, die vom Konstruktor aufgerufen wird, in eine separate Klasse zu abstrahieren. Sie übergeben dann eine Instanz dieser neuen Klasse an den Konstruktor Ihrer ursprünglichen abstrakten Klasse, wobei jede abgeleitete Klasse ihre eigene Version an den Basiskonstruktor übergibt. Es ist ein bisschen schwierig zu erklären, also werde ich ein Beispiel geben, basierend auf Ihrem.

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

In Ihrem speziellen Beispiel tut es ein wenig verwirrend und ich begann, aus vernünftigem Namen zu laufen, aber dies ist aufgrund der Tatsache, Ihnen bereits eine Verschachtelung von Anweisungen in Anweisungen, dh eine Anweisung, eine RealInstruction hat (oder zumindest optional); aber wie Sie sehen können, ist es immer noch möglich, das zu erreichen, was Sie wollen, und virtuelle Aufrufe von Mitgliedern eines Konstruktors zu vermeiden.

Falls das immer noch nicht klar ist, werde ich auch ein Beispiel geben, basierend auf einem, das ich kürzlich in meinem eigenen Code verwendet habe. In diesem Fall habe ich zwei Arten von Formularen, ein Header-Formular und ein Nachrichtenformular, die beide von einem Basisformular erben. Alle Formulare haben Felder, aber jeder Formulartyp hat einen anderen Mechanismus zum Konstruieren seiner Felder. Daher hatte ich ursprünglich eine abstrakte Methode namens GetOrderedFields, die ich vom Basiskonstruktor aus aufgerufen habe, und die Methode wurde in jeder abgeleiteten Formularklasse überschrieben. Dies gab mir die Warnung, die Sie nochmals erwähnen. Meine Lösung war das gleiche Muster wie oben und ist als

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

Ich habe vergessen zu erwähnen, aber ein weiterer Vorteil dieses Weges ist, dass Sie in Ihrem Fall vermeiden, dass die Basismethode virtuell statt abstrakt sein muss, deshalb erhalten Sie Kompilierzeitprüfung, anstatt eine Ausnahme zu werfen (wie @TarasDzyoba erwähnt) . – Richard

+0

Das ist sehr gut durchdacht, danke. Letztendlich habe ich dieses Problem auf andere Weise vermieden, aber das ist eine sehr gute Lösung für die Zukunft. –

1

Sie in der realen Anweisung in die Basisklassenkonstruktor passieren könnte:

protected Instruction(..., Instruction realInstruction) 
{ 
    //Some stuff 

    if (DoesUseRealInstruction) { 
     RealInstruction = realInstruction; 
    } 
} 

public DerivedInstruction(...) 
    : base(..., GetRealInstruction(...)) 
{ 
} 

Oder, wenn Sie wirklich eine virtuelle Funktion von Ihrem Konstruktor aufrufen möchten, die unterdrücken (was ich sehr discorage Sie aus), könnten Sie ReSharper Warnung:

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

Das würde gut funktionieren, aber es würde den Grund der Warnung nicht entfernen, verstecken Sie es einfach. Ich könnte es benutzen, wenn es keine anderen Lösungen gibt. Ich mag es, einmal zu arbeiten und Dinge zu setzen, um sich selbst einzurichten. Ich mag es nicht, dem Anrufer das Setup zu geben. –

+0

Antwort geändert. Aber ich denke, es ist immer noch nicht das, wonach du suchst. Und ich bezweifle, dass es eine solche Lösung gibt. Es gibt [einen guten Grund] (http://stackoverflow.com/questions/119506/virtual-member-call-in-a-constructor?rq=1) für die ReSharper-Warnung. –

+0

Ich verstehe. Ich habe den Aufruf 'GetRealInstruction' an eine andere Funktion übergeben, so dass sie genau dann aufgerufen wird, wenn sie benötigt wird. –

2

Wenn Sie eine Instanz der abgeleiteten Klasse zu erstellen, Ihr Call-Stack wird wie folgt aussehen:

GetRealInstruction() 
BaseContructor() 
DerivedConstructor() 

GetRealInstruction wird in der abgeleiteten Klasse überschrieben, deren Konstruktor noch nicht fertig ist.

Ich weiß nicht, wie Ihr anderer Code aussieht, aber Sie sollten zuerst prüfen, ob Sie in diesem Fall wirklich eine Elementvariable benötigen. Sie haben eine Methode, die das von Ihnen benötigte Objekt zurückgibt. Wenn Sie es wirklich brauchen, machen Sie eine Eigenschaft und rufen Sie GetRealInstruction() im Getter.

Auch Sie können GetRealInstruction abstrakt machen. Auf diese Weise müssen Sie die Ausnahme nicht auslösen, und der Compiler gibt Ihnen einen Fehler, wenn Sie vergessen, ihn in einer abgeleiteten Klasse zu überschreiben.

+0

Ich verstehe das Problem der aufrufenden Reihenfolge. Ich brauche nur GetRealInstruction in bestimmten abgeleiteten Klassen (diejenigen mit dem Bit doesRequireRealInstruction), also sollte ich es nicht abstrakt machen. Ich möchte nicht wirklich eine Eigenschaft dafür machen, weil ich die Zugriffs/Betriebsunterschiede zwischen Feldern und Methoden mag. (Ich verwende Eigenschaften in solchen Fällen, aber für viel einfachere Operationen.) Ich habe den Anruf anderswo verlegt. –

2

Sie können eine andere abstrakte Klasse RealInstructionBase einführen, damit Ihr Code aussehen wird:

public abstract class Instruction { 
    public Instruction() { 
     // do common stuff 
    } 
} 

public abstract class RealInstructionBase : Instruction { 
    public RealInstructionBase() : base() { 
     GetRealInstruction(); 
    } 

    protected abstract object GetRealInstruction(); 
} 

jeden Befehl nun die RealInstruction von RealInstructionBase leitet verwenden muss und alle anderen aus Instruction abzuleiten. Auf diese Weise sollten Sie alle korrekt initialisiert haben.

BEARBEITEN: Ok, das wird Ihnen nur ein saubereres Design geben (nein, wenn im Konstruktor), aber die Warnung nicht loswerden. Jetzt, wenn Sie wissen möchten, warum Sie die Warnung an erster Stelle erhalten, können Sie sich auf this question beziehen. Grundsätzlich ist der Punkt, dass Sie sicher sind, wenn Sie Ihre Klassen markieren, wo Sie die abstrakte Methode als versiegelt implementieren.

+0

Das ist zugegebenermaßen sauberer. Jedoch habe ich es geschafft, es zu sortieren, indem ich den 'GetRealInstruction'-Aufruf direkt vor dem Bedarf verschiebe, nicht im Konstruktor. Diese Funktion ist nicht zu teuer. –

0

folgt Eine weitere Option ist ein Initialize() Verfahren einzuführen, wo Sie alle Initialisierung zu tun, die ein voll ausgebautes Objekt erfordert.

Verwandte Themen