1490 Stimmen

Aufruf eines virtuellen Mitglieds in einem Konstruktor

Ich erhalte eine Warnung von ReSharper über einen Aufruf an ein virtuelles Mitglied aus meinem Objektkonstruktor.

Warum sollte man das nicht tun?

1289voto

Greg Beech Punkte 127525

Wenn ein in C# geschriebenes Objekt konstruiert wird, laufen die Initialisierer in der Reihenfolge von der am meisten abgeleiteten Klasse zur Basisklasse, und dann laufen die Konstruktoren in der Reihenfolge von der Basisklasse zur am meisten abgeleiteten Klasse ( siehe Blog von Eric Lippert für Details, warum das so ist ).

Auch in .NET ändern Objekte nicht den Typ, wenn sie konstruiert werden, sondern beginnen als der am meisten abgeleitete Typ, wobei die Methodentabelle für den am meisten abgeleiteten Typ gilt. Dies bedeutet, dass virtuelle Methodenaufrufe immer auf dem am weitesten abgeleiteten Typ ausgeführt werden.

Kombiniert man diese beiden Tatsachen, so ergibt sich das Problem, dass der Aufruf einer virtuellen Methode in einem Konstruktor, der nicht der am weitesten abgeleitete Typ in der Vererbungshierarchie ist, in einer Klasse aufgerufen wird, deren Konstruktor noch nicht ausgeführt wurde und die sich daher möglicherweise nicht in einem geeigneten Zustand für den Aufruf dieser Methode befindet.

Dieses Problem wird natürlich entschärft, wenn Sie Ihre Klasse als versiegelt markieren, um sicherzustellen, dass sie der am weitesten abgeleitete Typ in der Vererbungshierarchie ist - in diesem Fall ist es vollkommen sicher, die virtuelle Methode aufzurufen.

813voto

Matt Howells Punkte 38730

Um Ihre Frage zu beantworten, stellen Sie sich folgende Frage: Was wird der folgende Code ausgeben, wenn die Child Objekt instanziiert wird?

class Parent
{
    public Parent()
    {
        DoSomething();
    }

    protected virtual void DoSomething() 
    {
    }
}

class Child : Parent
{
    private string foo;

    public Child() 
    { 
        foo = "HELLO"; 
    }

    protected override void DoSomething()
    {
        Console.WriteLine(foo.ToLower()); //NullReferenceException!?!
    }
}

Die Antwort lautet, dass in der Tat eine NullReferenceException wird ausgelöst, weil foo null ist. Der Basiskonstruktor eines Objekts wird vor seinem eigenen Konstruktor aufgerufen . Mit einer virtual Aufruf im Konstruktor eines Objekts führen Sie die Möglichkeit ein, dass ererbte Objekte Code ausführen, bevor sie vollständig initialisiert wurden.

171voto

Lloyd Punkte 22223

Die Regeln von C# unterscheiden sich stark von denen von Java und C++.

Wenn Sie sich im Konstruktor für ein Objekt in C# befinden, existiert dieses Objekt in einer vollständig initialisierten (nur nicht "konstruierten") Form, als sein vollständig abgeleiteter Typ.

namespace Demo
{
    class A 
    {
      public A()
      {
        System.Console.WriteLine("This is a {0},", this.GetType());
      }
    }

    class B : A
    {      
    }

    // . . .

    B b = new B(); // Output: "This is a Demo.B"
}

Das bedeutet, dass eine virtuelle Funktion, die im Konstruktor von A aufgerufen wird, zu einer Überschreibungsfunktion in B führt, sofern eine solche vorhanden ist.

Selbst wenn Sie A und B absichtlich so einrichten und das Verhalten des Systems genau kennen, könnten Sie später einen Schock erleben. Nehmen wir an, Sie haben im Konstruktor von B virtuelle Funktionen aufgerufen, "wissend", dass diese von B oder A entsprechend behandelt werden würden. Dann vergeht die Zeit, und jemand anderes beschließt, C zu definieren und einige der virtuellen Funktionen dort zu überschreiben. Plötzlich ruft der Konstruktor von B Code in C auf, was zu ziemlich überraschendem Verhalten führen kann.

Es ist wahrscheinlich ohnehin eine gute Idee, virtuelle Funktionen in Konstruktoren zu vermeiden, da die Regeln sind so unterschiedlich zwischen C#, C++ und Java. Ihre Programmierer wissen vielleicht nicht, was sie erwarten können!

91voto

Ilya Ryzhenkov Punkte 11342

Die Gründe für die Warnung sind bereits beschrieben, aber wie würden Sie die Warnung beheben? Sie müssen entweder die Klasse oder das virtuelle Mitglied versiegeln.

  class B
  {
    protected virtual void Foo() { }
  }

  class A : B
  {
    public A()
    {
      Foo(); // warning here
    }
  }

Sie können Klasse A versiegeln:

  sealed class A : B
  {
    public A()
    {
      Foo(); // no warning
    }
  }

Oder Sie können die Methode Foo versiegeln:

  class A : B
  {
    public A()
    {
      Foo(); // no warning
    }

    protected sealed override void Foo()
    {
      base.Foo();
    }
  }

24voto

Alex Lyman Punkte 15257

In C# läuft der Konstruktor einer Basisklasse vor des Konstruktors der abgeleiteten Klasse, so dass alle Instanzfelder, die eine abgeleitete Klasse in dem möglicherweise überschriebenen virtuellen Mitglied verwenden könnte, noch nicht initialisiert sind.

Bitte beachten Sie, dass dies nur eine Warnung damit du aufpasst und sicherstellst, dass alles in Ordnung ist. Es gibt tatsächlich Anwendungsfälle für dieses Szenario, man muss nur das Verhalten dokumentieren des virtuellen Mitglieds, dass es keine Instanzfelder verwenden kann, die in einer abgeleiteten Klasse unterhalb des Konstruktors, der es aufruft, deklariert sind.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X