Frage

Ich erhalte eine Warnung von ReSharper über einen Anruf zu einem virtuellen Mitglied aus meinen Objekten Konstruktor.

Warum sollte dies sein, etwas nicht zu tun?

War es hilfreich?

Lösung

Wenn ein Objekt in C # geschrieben aufgebaut ist, was passiert, ist, dass die Initialisierungen laufen, um von der am meisten abgeleiteten Klasse auf die Basisklasse und dann Konstruktoren, um von der Basisklasse auf die meisten abgeleiteten Klasse ( sehen Eric Lippert Blog für Details, warum dies ist ).

Auch in .NET-Objekte nicht Art ändern, wie sie aufgebaut sind, aber als die abgeleiteten Typ beginnen, mit der Methodentabelle für die meisten abgeleiteten Typ zu sein. Dies bedeutet, dass virtuelle Methode fordert den meisten abgeleiteten Typen immer ausgeführt werden.

Wenn Sie kombinieren diese zwei Fakten, die Sie mit dem Problem gelassen werden, dass, wenn Sie einen virtuellen Methodenaufruf in einem Konstruktor machen, und es ist nicht der abgeleiteten Typ in seiner Vererbungshierarchie, dass es auf einer Klasse aufgerufen wird, dessen Konstruktor wurde nicht ausgeführt, und daher nicht in einem geeigneten Zustand sein kann, genannt das Verfahren zu haben.

Dieses Problem ist natürlich, gemildert, wenn Sie Ihre Klasse markieren, wie versiegelt, um sicherzustellen, dass es die abgeleiteten Typ in der Vererbungshierarchie ist -. In diesem Fall ist es absolut sicher ist die virtuelle Methode aufrufen

Andere Tipps

Um Ihre Frage zu beantworten, sollten Sie diese Frage: was wird der Code unten drucken, wenn das 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 ist, dass ein NullReferenceException tatsächlich geworfen werden, weil foo null ist. Ein Basiskonstruktor des Objekts vor seinem eigenen Konstruktor aufgerufen . Indem Konstruktor einen virtual Anruf in einem Objekt die Möglichkeit einführen, dass erben Objekte Code ausführen, bevor sie vollständig initialisiert wurde.

Die Regeln von C # sind sehr verschieden von der Java und C ++.

Wenn Sie im Konstruktor für ein Objekt in C # sind, das Objekt existiert in einer vollständig initialisiert (nur nicht „konstruiert“) Form, als vollständig abgeleiteten 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, wenn Sie eine virtuelle Funktion aus dem Konstruktor von A nennen, es auf jede Überschreibung in B lösen wird, wenn man zur Verfügung gestellt wird.

Auch wenn Sie absichtlich A und B wie folgt aufgebaut, vollständig das Verhalten des Systems zu verstehen, könnten Sie später für einen Schock sein. Sagen Sie virtuelle Funktionen in B Konstruktor, „zu wissen,“ sie würden behandelt werden durch B oder A als angemessen bezeichnet. Dann die Zeit vergeht, und jemand anderes entscheidet, die sie benötigen, C, zu definieren und dort einige der virtuellen Funktionen außer Kraft setzen. Aufruf Code in C ganz plötzlich Konstruktor B endet, die zu ganz überraschende Verhalten führen kann.

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

Die Gründe der Warnung sind bereits beschrieben, aber wie würden Sie die Warnung zu beheben? Sie müssen entweder Klasse oder virtuelles Mitglied versiegeln.

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

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

Sie können Klasse A Dichtung:

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

Sie können auch Methode Foo abdichten:

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

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

In C #, eine Basisklasse Konstruktor läuft vor die abgeleitete Klasse Konstruktor, so dass alle Instanzfelder, die eine abgeleitete Klasse in dem möglicherweise-überschrieben virtuellen Mitglied initialisiert werden noch nicht verwenden kann.

Sie beachten, dass dies nur ein Warnung , um Sie aufmerksam zu machen, und stellen Sie sicher, es ist alles richtig. Es gibt tatsächliche Anwendungsfälle für dieses Szenario, müssen Sie nur auf Dokument, das Verhalten des virtuellen Element, das es nicht unter alle Instanzfelder erklärt in einer abgeleiteten Klasse verwenden können, wo der Konstruktor aufrufen es ist.

Es gibt gut geschriebene Antworten oben, warum Sie nicht das tun wollen. Hier ist ein Gegenbeispiel, wo vielleicht Sie würde will tun, dass (übersetzt in C # von Praktisches Objektorientiertes Design in Ruby von Sandi Metz, Seite 126).

Beachten Sie, dass GetDependency() ist keine Instanzvariablen nicht zu berühren. Es wäre statisch, wenn statische Methoden virtuell sein könnten.

(Um fair zu sein, gibt es wahrscheinlich intelligentere Wege, dies zu tun über Dependency Injection Container oder Objektinitialisierer ...)

public class MyClass
{
    private IDependency _myDependency;

    public MyClass(IDependency someValue = null)
    {
        _myDependency = someValue ?? GetDependency();
    }

    // If this were static, it could not be overridden
    // as static methods cannot be virtual in C#.
    protected virtual IDependency GetDependency() 
    {
        return new SomeDependency();
    }
}

public class MySubClass : MyClass
{
    protected override IDependency GetDependency()
    {
        return new SomeOtherDependency();
    }
}

public interface IDependency  { }
public class SomeDependency : IDependency { }
public class SomeOtherDependency : IDependency { }

Ja, es ist in der Regel schlecht virtuelle Methode im Konstruktor aufrufen.

An diesem Punkt kann das objet noch nicht vollständig aufgebaut werden, und die durch Verfahren erwartet Invarianten kann noch nicht halten.

Weil, bis die Konstruktor Ausführung beendet hat, wird das Objekt nicht vollständig instanziiert. Irgendwelche Mitglieder durch die virtuelle Funktion referenziert kann nicht initialisiert werden. In C ++, wenn Sie in einem Konstruktor sind, this bezieht sich nur auf den statischen Typen des Konstruktors Sie sind, und nicht der tatsächliche dynamische Typ des Objekts, das erstellt wird. Dies bedeutet, dass der virtuelle Funktionsaufruf nicht einmal gehen könnte, wo man es erwarten.

Ihr Konstruktor kann (später in einer Erweiterung Ihrer Software) aus dem Konstruktor einer Unterklasse aufgerufen werden, das die virtuelle Methode überschreibt. Jetzt ist nicht die Implementierung der Funktion der Unterklasse, aber die Implementierung der Basisklasse aufgerufen. So ist es nicht wirklich Sinn machen, hier eine virtuelle Funktion aufzurufen.

Allerdings, wenn Ihr Design des Liskov Substitution Prinzip erfüllt, wird kein Schaden angerichtet. Wahrscheinlich ist das, warum es toleriert wird -. Eine Warnung, kein Fehler

Ein wichtiger Aspekt dieser Frage, die anderen Antworten haben noch nicht angegangen ist, dass es für eine Basisklasse ist sicher von virtuellen Mitglieder zu rufen in ihrem Konstruktor , wenn das ist, was die abgeleiteten Klassen erwarten, es zu tun . In solchen Fällen ist der Designer der abgeleiteten Klasse verantwortlich dafür, dass alle Verfahren, die ausgeführt werden, bevor der Bau abgeschlossen ist, wie vernünftig verhalten, wie sie unter den gegebenen Umständen möglich. Zum Beispiel in C ++ / CLI, sind Konstrukteure in Code gewickelt, die auf dem teilweise Dispose aufgebauten Objekt aufrufen, wenn Konstruktion ausfällt. Der Aufruf Dispose in solchen Fällen ist oft notwendig, um Ressourcenlecks zu verhindern, aber Dispose Methoden müssen die Möglichkeit vorbereitet werden, dass das Objekt, auf das sie ausgeführt werden möglicherweise nicht vollständig aufgebaut worden ist.

Die Warnung ist eine Erinnerung daran, dass virtuelle Mitglieder wahrscheinlich auf abgeleiteten Klasse überschrieben werden. In diesem Fall unabhängig von der übergeordneten Klasse tat, um ein virtuelles Mitglied wird durch zwingendes Kind Klasse rückgängig gemacht oder geändert werden. Schauen Sie sich das kleine Beispiel Schlag für Klarheit

Die Elternklasse unten versucht, auf seinem Konstruktor Wert auf ein virtuelles Element einzustellen. Und dies löst erneut schärfere Warnung, lassen Sie auf Code finden Sie unter:

public class Parent
{
    public virtual object Obj{get;set;}
    public Parent()
    {
        // Re-sharper warning: this is open to change from 
        // inheriting class overriding virtual member
        this.Obj = new Object();
    }
}

Das Kind Klasse hier überschreibt die übergeordnete Eigenschaft. Wenn diese Eigenschaft nicht virtuell markiert wurde der Compiler würde davor warnen, dass das Eigentum versteckt Eigentum auf der übergeordneten Klasse und lassen vermuten, dass Sie ‚neuen‘ Schlüsselwort hinzufügen, wenn es beabsichtigt ist.

public class Child: Parent
{
    public Child():base()
    {
        this.Obj = "Something";
    }
    public override object Obj{get;set;}
}

Schließlich sind die Auswirkungen auf den Einsatz, verlässt die Ausgabe des folgenden Beispiel wird die Anfangs von übergeordneten Klasse Konstruktor eingestellten Wert. Und das ist, was Re-schärfere Versuche Sie Werte auf zu warnen, die das Kind Klassenkonstruktors überschrieben auf der übergeordneten Klasse Konstruktor sind offen, die direkt nach der übergeordneten Klasse Konstruktor aufgerufen wird .

public class Program
{
    public static void Main()
    {
        var child = new Child();
        // anything that is done on parent virtual member is destroyed
        Console.WriteLine(child.Obj);
        // Output: "Something"
    }
} 

Vorsicht vor blind zu folgen ReSharper Rat und machte die Klasse versiegelt! Wenn es sich um ein Modell in EF-Code ist erstes wird das virtuelle Schlüsselwort entfernen, und das wäre ein träges Laden von seinen Beziehungen deaktivieren.

    public **virtual** User User{ get; set; }

Eine wichtige fehlende Bit ist, was der richtige Weg ist, um dieses Problem zu lösen?

Wie Greg erklärt, hier das eigentliche Problem ist, dass ein Basisklassenkonstruktor würde das virtuelle Mitglied vor der abgeleiteten Klasse aufrufen konstruiert worden ist.

Der folgende Code, genommen von MSDN Konstruktor Design-Richtlinien , zeigt dieses Problem.

public class BadBaseClass
{
    protected string state;

    public BadBaseClass()
    {
        this.state = "BadBaseClass";
        this.DisplayState();
    }

    public virtual void DisplayState()
    {
    }
}

public class DerivedFromBad : BadBaseClass
{
    public DerivedFromBad()
    {
        this.state = "DerivedFromBad";
    }

    public override void DisplayState()
    {   
        Console.WriteLine(this.state);
    }
}

Wenn eine neue Instanz von DerivedFromBad erstellt wird, ruft die Basisklasse Konstruktor DisplayState und zeigt BadBaseClass, da das Feld Update von dem abgeleiteten Konstruktor noch nicht gegeben hat.

public class Tester
{
    public static void Main()
    {
        var bad = new DerivedFromBad();
    }
}

Eine verbesserte Implementierung entfernt die virtuelle Methode aus dem Basisklassenkonstruktor, und verwendet ein Initialize Verfahren. Erstellen eine neue Instanz von DerivedFromBetter zeigt der erwartete „DerivedFromBetter“

public class BetterBaseClass
{
    protected string state;

    public BetterBaseClass()
    {
        this.state = "BetterBaseClass";
        this.Initialize();
    }

    public void Initialize()
    {
        this.DisplayState();
    }

    public virtual void DisplayState()
    {
    }
}

public class DerivedFromBetter : BetterBaseClass
{
    public DerivedFromBetter()
    {
        this.state = "DerivedFromBetter";
    }

    public override void DisplayState()
    {
        Console.WriteLine(this.state);
    }
}

Es gibt einen Unterschied zwischen C ++ und C # in diesem speziellen Fall. In C ++ wird das Objekt nicht initialisiert, und daher ist es nicht sicher eine virutal Funktion in einem Konstruktor aufzurufen. In C #, wenn ein Klassenobjekt erstellt alle seine Mitglieder sind Null initialisiert. Es ist möglich, eine virtuelle Funktion in dem Konstruktor zu nennen, aber wenn Sie Mitglieder zugreifen können werden, die immer noch Null sind. Wenn Sie an die Mitglieder nicht zugreifen es ist ganz sicher eine virtuelle Funktion in C # zu nennen.

Nur meine Gedanken hinzuzufügen. Wenn Sie immer auf den privaten Bereich initialisieren, wenn es zu definieren, soll dieses Problem vermeiden. Mindestens Code unten funktioniert wie ein Charme:

class Parent
{
    public Parent()
    {
        DoSomething();
    }
    protected virtual void DoSomething()
    {
    }
}

class Child : Parent
{
    private string foo = "HELLO";
    public Child() { /*Originally foo initialized here. Removed.*/ }
    protected override void DoSomething()
    {
        Console.WriteLine(foo.ToLower());
    }
}

Eine weitere interessante Sache, die ich gefunden, dass ReSharper Fehler können wie unter dem etwas ‚zufrieden‘ werden, indem Sie ist stumm zu mir (aber, wie von vielen bereits erwähnt, ist es immer noch keine gute Idee, virtuelle prop / Methoden aufrufe in ctor.

public class ConfigManager
{

   public virtual int MyPropOne { get; private set; }
   public virtual string MyPropTwo { get; private set; }

   public ConfigManager()
   {
    Setup();
   }

   private void Setup()
   {
    MyPropOne = 1;
    MyPropTwo = "test";
   }

}

Ich würde nur ein Initialize () -Methode der Basisklasse hinzufügen und dann das nennen, abgeleitet von Konstrukteuren. Das Verfahren wird, nachdem alle der Konstrukteure haben alle virtuellen / abstrakte Methoden / Eigenschaften aufrufen wurde ausgeführt:)

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top