Virtuelle member-Aufruf in einem Konstruktor
Ich bin immer eine Warnung von ReSharper über einen Anruf zu einem virtuellen Mitglied aus meiner Objekte Konstruktor.
Warum sollte dies etwas sein, das nicht zu tun?
Ernsthaft.. Ihr Kommentar sollte die Antwort hier. Während Greg Erklärung richtig ist, habe ich nicht verstanden, bis ich Lesen Ihren blog.
Ihre domain ist abgelaufen.
Sie finden den Artikel von @m.edmondson jetzt hier: codeproject.com/Articles/802375/...
Ihre domain ist abgelaufen.
Sie finden den Artikel von @m.edmondson jetzt hier: codeproject.com/Articles/802375/...
InformationsquelleAutor JasonS | 2008-09-23
Du musst angemeldet sein, um einen Kommentar abzugeben.
Wenn ein Objekt in C# geschrieben ist konstruiert, was passiert, ist, dass die Initialisierungen ausgeführt, in der Reihenfolge von der am meisten abgeleiteten Klasse auf die Basisklasse, und dann den Konstruktoren ausgeführt werden, um von der Basisklasse an die abgeleitete Klasse (siehe Eric Lippert ' s blog für details, warum dies).
Auch in .NET-Objekte nicht ändern die Art, wie Sie konstruiert sind, aber beginnen, wie die meisten abgeleiteten Typ, mit der Methode der Tabelle, die für die meisten abgeleiteten Typ. Dies bedeutet, dass virtuelle Methodenaufrufe laufen immer auf die abgeleiteten Typ.
Wenn Sie kombinieren diese beiden Faktoren bleibt das problem, dass, wenn Sie einen virtuellen Methodenaufruf im Konstruktor, und es ist nicht die abgeleiteten Typ in seiner Vererbungshierarchie, dass es genannt werden, die auf eine Klasse, deren Konstruktor nicht ausgeführt wurde, und darf daher nicht in einem geeigneten Zustand zu haben, dass die Methode aufgerufen wird.
Dieses problem wird natürlich gemindert, wenn Sie markieren Sie Ihre Klasse als versiegelt, um sicherzustellen, dass es ist die am meisten abgeleiteten Typ in der Vererbungshierarchie - in dem Fall ist es vollkommen sicher zum Aufruf der virtuellen Methode.
Wenn Sie sicherstellen möchten, dass eine abgeleitete Klasse kann nicht weiter abgeleitet, es ist durchaus akzeptabel, um es zu verschließen.
Der Punkt ist, dass abgeschlossen haben, die sich die virtuellen Mitglieder des base - Klasse[es], und somit sind die Kennzeichnung der Klasse als vollständig abgeleitet, wie Sie es haben wollen.
Wenn Sie die virtuelle Methode, die das Verhalten hat nichts zu tun mit der Instanz-Variablen, ist das nicht ok? Wie es scheint, vielleicht sollten wir in der Lage sein, zu erklären, dass eine virtuelle Methode wird nicht ändern Sie den Instanz-Variablen? (statische?) Zum Beispiel, wenn Sie möchten, um eine virtuelle Methode, die überschrieben werden können, zu instanziieren, die einen mehr abgeleiteten Typ. Dies scheint sicher zu sein mich, und garantiert nicht diese Warnung.
Wenn Sie möchten, rufen Sie eine virtuelle Methode ist in die am meisten abgeleitete Klasse, erhalten Sie noch die Warnung, während Sie wissen, es ist nicht ein problem verursachen. In diesem Fall könnten Sie teilen Ihre Kenntnisse mit dem system von Abdichtung, Klasse.
InformationsquelleAutor Greg Beech
Um deine Frage zu beantworten, betrachten Sie diese Frage: was wird den code unten drucken, wenn die
Child
Objekt instanziiert wird?Die Antwort ist, dass in der Tat eine
NullReferenceException
geworfen werden, weilfoo
null ist. Ein Objekt der Basis-Konstruktor wird aufgerufen, bevor der eigene Konstruktor. Durch einevirtual
Aufruf in einem Objekt-Konstruktor übergeben Sie die Einführung der Möglichkeit, dass die Erben Objekte code auszuführen, bevor Sie vollständig initialisiert ist.Dies ist klarer als die obige Antwort. Ein Beispiel-code sagt mehr als tausend Worte.
Ich bin damit einverstanden, schönes Beispiel..
InformationsquelleAutor Matt Howells
Die Regeln von C# sind sehr Verschieden von der Java und C++.
Wenn Sie im Konstruktor für ein Objekt in C#, das Objekt befindet sich in einer vollständig initialisiert ist (eben nicht "konstruiert") zu bilden, eine voll abgeleiteten Typ.
Dies bedeutet, dass, wenn ein Aufruf einer virtuellen Funktion vom Konstruktor von A, es löst auf jeden override in B, falls eine angegeben ist.
Selbst wenn Sie absichtlich eingestellt A und B, wie dies richtig zu verstehen, das Verhalten des Systems, könnten Sie in für ein Schock, später. Sagen Sie, dass Sie als virtuelle Funktionen in B-Konstruktor, "wissen" Sie würden behandelt werden, indem Sie B oder A als angemessen. Dann vergeht die Zeit, und jemand anderes entscheidet, die Sie benötigen, um zu definieren, C, und überschreiben von virtuellen Funktionen gibt. Plötzlich B Konstruktor endet Aufruf von code in C, was dazu führen könnte, ganz überraschend Verhalten.
Ist es wahrscheinlich eine gute Idee, um virtuelle Funktionen in Konstruktoren sowieso, da die Regeln sind so unterschiedlich zwischen C#, C++ und Java. Ihr Programmierer kann nicht wissen, was zu erwarten ist!
Eigentlich die Regeln in Java sind die gleichen.
C++ ist sehr unterschiedlich eigentlich. Virtuelle Methodenaufrufe in Konstruktoren (und Destruktoren!) behoben werden mit dem Typ (und vtable), die derzeit gebaut wird, nicht die abgeleiteten Typ, wie Java und C# beide tun. Hier ist der entsprechende FAQ-Eintrag.
Sie sind absolut richtig. Es ist schon eine Weile her, seit ich codiert in C++ und ich irgendwie verwirrt das alles. Soll ich löschen den Kommentar, um zu vermeiden, verwirrend, wer sonst?
Es ist eine wichtige Möglichkeit, in der C# unterscheidet sich von Java und VB.NET; in C#, die Felder, die initialisiert sind, an der Stelle der Deklaration haben Ihre Initialisierungen verarbeitet werden, bevor die Basis-Konstruktor aufrufen; dies geschah zu dem Zweck, dem abgeleiteten Klasse von Objekten verwendbar sein, die vom Konstruktor, aber leider diese Möglichkeit funktioniert nur für derived-class-features, deren Initialisierung ist nicht gesteuert, die von jeder abgeleiteten Klasse Parameter.
InformationsquelleAutor Lloyd
Gründe für die Warnung sind bereits beschrieben, aber wie würde man das lösen die Warnung? Sie haben zu versiegeln, entweder Klasse oder virtuellen member.
Können Sie seal-Klasse A:
Oder Sie können Siegel Methode Foo:
InformationsquelleAutor Ilya Ryzhenkov
In C#, eine Basisklasse' Konstruktor läuft vor der abgeleiteten Klasse der Konstruktor, also alle Instanz-Felder, die eine abgeleitete Klasse verwenden könnte, in der möglicherweise überschrieben virtuellen member sind noch nicht initialisiert.
Tun, beachten Sie, dass dies nur ein Warnung zu machen, Sie achten und sicherstellen, dass er alle Rechte. Es sind tatsächliche Anwendungsfälle für dieses Szenario, Sie müssen nur Dokument das Verhalten der virtuellen member, es kann keine Instanz-Felder deklariert, die in einer abgeleiteten Klasse unter denen der Konstruktor-Aufruf ist es.
InformationsquelleAutor Alex Lyman
Gibt es gut geschriebene Antworten, warum Sie würde nicht wollen, das zu tun. Hier ist ein Gegenbeispiel, wo Sie vielleicht würde wollen, das zu tun (übersetzt in C# aus Practical Object-Oriented Design in Ruby von Sandi Metz, p. 126).
Beachten Sie, dass
GetDependency()
ist nicht die Berührung der Instanz-Variablen. Wäre es statisch ist, wenn Sie statische Methoden virtuelle.(Um fair zu sein, wahrscheinlich gibt es intelligentere Möglichkeiten, dies zu tun, die über dependency injection Container oder Objekt-Initialisierungen...)
Ich Wünsche der .NET Framework hatte, statt den meist nutzlosen
Finalize
als Standard-MitgliedObject
, verwendet hatte, die vtable-slot für eineManageLifetime(LifetimeStatus)
Methode, die erhalten würde, die aufgerufen wird, wenn ein Konstruktor gibt, um client-code, wenn ein Konstruktor wirft, oder wenn ein Objekt gefunden ist, verlassen zu werden. Die meisten Szenarien, die bedeuten würde Aufruf einer virtuellen Methode aus einer Basisklasse Konstruktor werden könnte, am besten mit zwei-Stufen-Konstruktion, aber zwei-Stufen-Konstruktion sollte so Verhalten, wie eine Implementierung detail, sondern als eine Anforderung, dass die Kunden berufen sich auf die zweite Stufe.Noch, Probleme auftreten können, die mit diesem code nur wie jeden anderen Fall in diesem thread;
GetDependency
ist nicht garantiert, um sicher zu sein für die Berufung vorMySubClass
Konstruktor aufgerufen wurde. Auch, mit Standard-Abhängigkeiten instanziiert standardmäßig nicht ist, was Sie nennen würde ein "reines DI".Das Beispiel "Abhängigkeit outjection". 😉 Für mich ist dies ein weiterer guter counter-Beispiel für einen virtuellen Methodenaufruf aus dem Konstruktor. SomeDependency ist nicht mehr instanziiert in MySubClass Ableitungen führt zu gebrochenen Verhalten für jeden MyClass feature, das hängt davon ab, SomeDependency.
InformationsquelleAutor Josh Kodroff
Ja, es ist generell schlecht zum Aufruf der virtuellen Methode in den Konstruktor.
Zu diesem Zeitpunkt, das objet möglicherweise nicht vollständig aufgebaut noch, und die Invarianten erwartet von Methoden können nicht halten noch.
InformationsquelleAutor David Pierre
Da, bis der Konstruktor abgeschlossen hat, die Ausführung, das Objekt ist nicht vollständig instanziiert werden. Alle Mitglieder verwiesen, die durch die virtuelle Funktion kann nicht initialisiert werden. In C++, wenn man in einem Konstruktor
this
bezieht sich nur auf den statischen Typ des Konstruktors Sie sind, und nicht der tatsächliche dynamische Typ des Objekts, das erstellt wird. Dies bedeutet, dass die virtuelle Funktion Aufruf vielleicht gar nicht gehen, wo Sie es erwarten.InformationsquelleAutor 1800 INFORMATION
Konstruktor kann (später, in eine Erweiterung Ihrer software) aufgerufen werden aus dem Konstruktor einer Unterklasse überschreibt die virtuelle Methode. Jetzt nicht in der Subklasse die Implementierung der Funktion, aber die Umsetzung von der Basis-Klasse aufgerufen werden. So ist es nicht wirklich sinnvoll zum aufrufen einer virtuellen Funktion hier.
Allerdings, wenn Ihr design erfüllt das Liskov-Substitution-Prinzip, keinen Schaden getan. Wahrscheinlich das ist, warum es geduldet - eine Warnung, kein Fehler.
InformationsquelleAutor xtofl
Ein wichtiger Aspekt dieser Frage, die anderen Antworten noch nicht angegangen ist, dass es sicher ist, für eine Basis-Klasse aufrufen virtuellen Mitglieder von innerhalb des Konstruktors , wenn die abgeleitete Klassen sind zu erwarten, es zu tun. In solchen Fällen, die designer die abgeleitete Klasse ist dafür verantwortlich, dass alle Methoden, die ausgeführt werden, bevor der Bau abgeschlossen ist wird es sich Verhalten, wie sinnvoll, wie Sie unter den gegebenen Umständen können. Zum Beispiel in C++/CLI, Konstruktoren sind verpackt in code aufgerufen
Dispose
auf die teilweise konstruierte Objekt, wenn der Bau scheitert. AufrufDispose
in solchen Fällen ist oft notwendig, um zu verhindern, dass Ressourcen-Lecks, aberDispose
Methoden müssen auf die Möglichkeit vorbereitet sein, dass das Objekt, auf dem Sie ausgeführt werden, kann nicht vollständig aufgebaut.InformationsquelleAutor supercat
Die Warnung ist eine Erinnerung daran, dass die virtuellen Mitglieder sind wahrscheinlich überschrieben werden, der auf abgeleitete Klasse. In diesem Fall unabhängig von der übergeordneten Klasse hat zu einem virtuellen Mitglied werden rückgängig gemacht oder geändert durch überschreiben der Kind-Klasse. Blick auf die kleine Beispiel Schlag für Klarheit
Die übergeordnete Klasse von unten versuche, den Wert zu einem virtuellen Mitglied auf seinen Konstruktor. Und dies löst Wieder schärfer Warnung, lass sehen, on-code:
Die Kind-Klasse hier überschreibt die übergeordnete Eigenschaft. Wenn diese Eigenschaft nicht markiert war virtuelle würde der compiler warnen, dass die Eigenschaft versteckt-Eigenschaft der übergeordneten Klasse und zu empfehlen, dass Sie hinzufügen, die "neue" Schlüsselwort, wenn es absichtlich ist.
Schließlich die Auswirkungen auf die Nutzung, die Ausgabe des Beispiels unten gibt den Anfangswert gesetzt, die von übergeordneten Konstruktor der Klasse.
Und dies ist es, was Re-schärfere versuche, Sie zu warnen,, Werte auf der Übergeordneten Klasse Konstruktor geöffnet sind, überschrieben werden, indem das Kind Konstruktor der Klasse, die aufgerufen wird, direkt nach dem Eltern-Konstruktor der Klasse.
InformationsquelleAutor BTE
Hüten, sich blind folgenden Resharper Beratung und die Klasse ist versiegelt!
Wenn es ein Modell im EF Code First wird es entfernen Sie das Schlüsselwort virtual, und das wäre deaktivieren Sie verzögertes laden der Beziehungen.
InformationsquelleAutor typhon04
Einen wichtigen fehlenden Teil ist, was ist der richtige Weg, um dieses Problem zu beheben?
Als Greg erklärt, das root-problem ist hier, dass ein Basisklassenkonstruktor aufrufen würden die virtuellen member, bevor der abgeleiteten Klasse konstruiert wurde.
Den folgenden code, übernommen aus MSDN-Konstruktor-design-Richtlinien, veranschaulicht dieses Problem.
Wenn eine neue Instanz von
DerivedFromBad
erstellt wird, die die Basisklasse Konstruktor ruft zuDisplayState
und zeigtBadBaseClass
weil das Feld noch nicht aktualisiert, indem der abgeleitete Konstruktor.Einer verbesserten Umsetzung entfernt die virtuelle Methode aus der Basisklasse Konstruktor, und verwendet eine
Initialize
Methode. Erstellen einer neuen Instanz derDerivedFromBetter
zeigt das erwartete "DerivedFromBetter"Definieren Sie einen geschützten Konstruktor in der BetterBaseClass Klasse, hat eine zusätzliche
bool initialize
parameter, der bestimmt, obInitialize
heißt in der Basis-Konstruktor. Der abgeleitete Konstruktor wäre dann rufen Siebase(false)
zu vermeiden, fordern Initialisieren zweimalabsolut! Habe ich behoben mit Ihrer Beobachtung. Danke!
Und das funktioniert nicht. Die Basisklasse noch Anrufe DisplayState vor der DerivedFromBetter Konstruktor ausgeführt wurde, so gibt es "BetterBaseClass".
InformationsquelleAutor Gustavo Mori
Gibt es einen Unterschied zwischen C++ und C# in diesem speziellen Fall.
In C++ das Objekt ist nicht initialisiert und daher ist es riskant zu nennen eine virtueller-Funktion in einem Konstruktor.
In C# wird ein class-Objekt wird erstellt, alle Mitglieder sind gleich null initialisiert. Es ist möglich, zum aufrufen einer virtuellen Funktion im Konstruktor, aber wenn Sie vielleicht den Zugriff auf Mitglieder, die noch immer auf null. Wenn Sie keinen Zugriff auf die Mitglieder ist es ganz sicher zum aufrufen einer virtuellen Funktion in C#.
Das gleiche argument gilt für C++, wenn Sie nicht brauchen, um den Zugriff auf Mitglieder, Sie nicht kümmern, Sie waren nicht initialisiert...
Nein. Beim Aufruf einer virtuellen Methode in einen Konstruktor in C++ es wird aufgerufen, nicht die tiefste überschrieben Umsetzung, aber die version im Zusammenhang mit den aktuellen Typ. Es heißt praktisch, aber als wenn auf einen Typ der aktuellen Klasse, - die Sie nicht haben Zugriff auf Methoden und member der abgeleiteten Klasse.
InformationsquelleAutor Yuval Peled
Nur meine Gedanken. Wenn Sie immer initialisieren den privaten Bereich, wenn es definieren, dieses problem sollte vermieden werden. Mindestens folgende code funktioniert wie ein Charme:
InformationsquelleAutor Jim Ma
Andere interessante Sache, die ich fand, ist, dass ReSharper Fehler können 'zufrieden' von etwas wie unten, das ist dumm für mich (allerdings, wie bereits von vielen erwähnt, es ist immer noch nicht eine gute Idee zu nennen, virtuelle requisite/Methoden im Tor.
}
Ich Stimme @alzaimar! Ich versuche, verlassen Sie die Optionen für die Personen, die mit ähnlichen Problem-und wer möchte nicht zu implementieren die Lösungen in den oben genannten, wahrscheinlich aufgrund einiger Einschränkungen. Mit diesem (wie ich bereits in mein workaround oben), eine andere Sache, die ich versuche zu zeigen ist, dass ReSharper, wenn möglich, muss in der Lage sein, Kennzeichnen Sie diese Problemumgehung als Fehler zu. Allerdings ist es derzeit nicht, was dazu führen könnte, zwei Dinge, die Sie vergessen haben zu diesem Szenario oder wollten Sie bewusst lassen Sie es für einige gültige use-case kann man nicht denken, jetzt.
InformationsquelleAutor adityap
Ich möchte nur hinzufügen, eine Initialize () - Methode der Basisklasse und dann rufen, die von abgeleiteten Konstruktoren. Diese Methode rufen Sie eine virtuelle/abstrakte Methoden/Eigenschaften NACHDEM alle Konstruktoren ausgeführt worden 🙂
InformationsquelleAutor Ross