Soll ich alle Methoden virtuell markieren?

74

In Java können Sie die Methode als endgültig markieren, um ein Überschreiben zu verhindern.

In C # müssen Sie die Methode als virtuell markieren , es zu machen möglich außer Kraft zu setzen.

Bedeutet dies, dass Sie in C # alle Methoden als virtuell markieren sollten (mit Ausnahme einiger Methoden, die nicht überschrieben werden sollen), da Sie höchstwahrscheinlich nicht wissen, auf welche Weise Ihre Klasse vererbt werden kann?

Georgii Oleinikov
quelle
1
Ich denke gerne, dass Sie in C # etwas falsch machen, wenn Sie etwas tun, das sich anspruchsvoll anfühlt.
Louis Kottmann
4
@KonradRudolph, Zitat / Quelle bitte!
Bests
1
@bestsss Nein, das tue ich definitiv nicht. Endgültige Felder sind völlig unabhängig.
Konrad Rudolph
1
@bestsss Vergiss die finalFelder, ich weiß, dass er über sie spricht. Aber ich denke , dass er auch über endgültige Methoden in spricht Effective Java . Ich kann mich jedoch falsch daran erinnern. Ich bin mir aber sicher, dass er es damals woanders erwähnt hat - vielleicht in einem seiner Gespräche.
Konrad Rudolph
2
Lassen Sie uns diese Diskussion im Chat fortsetzen
bestsss

Antworten:

140

In C # müssen Sie die Methode als virtuell markieren, damit sie überschrieben werden kann. Bedeutet dies, dass Sie in C # alle Methoden als virtuell markieren sollten (mit Ausnahme einiger Methoden, die nicht überschrieben werden sollen), da Sie höchstwahrscheinlich nicht wissen, auf welche Weise Ihre Klasse vererbt werden kann?

Nein. Wenn die Sprachdesigner der Meinung wären , dass virtuell die Standardeinstellung sein sollte , wäre dies die Standardeinstellung gewesen .

Überschreibbarkeit ist eine Funktion , die wie alle Funktionen Kosten verursacht . Die Kosten einer überschreibbaren Methode sind beträchtlich: Es entstehen hohe Kosten für Design, Implementierung und Test, insbesondere wenn die Klasse "empfindlich" ist. Virtuelle Methoden sind Möglichkeiten, nicht getesteten Code von Drittanbietern in ein System einzuführen, was sich auf die Sicherheit auswirkt.

Wenn Sie nicht wissen, wie Ihre Klasse vererbt werden soll , veröffentlichen Sie Ihre Klasse nicht, da Sie das Entwerfen noch nicht abgeschlossen haben. Ihr Erweiterbarkeitsmodell sollten Sie unbedingt im Voraus kennen. Dies sollte Ihre Entwurfs- und Teststrategie stark beeinflussen.

Ich befürworte, dass alle Klassen versiegelt und alle Methoden nicht virtuell sind, bis Sie einen realen, kundenorientierten Grund haben, eine Methode zu entsiegeln oder virtuell zu machen.

Grundsätzlich lautet Ihre Frage: "Ich weiß nicht, wie meine Kunden meine Klasse konsumieren möchten. Sollte ich sie daher willkürlich erweiterbar machen?" Nein; Sie sollten sich auskennen ! Sie würden nicht fragen: "Ich weiß nicht, wie meine Kunden meine Klasse verwenden werden. Soll ich also alle meine Eigenschaften schreibgeschützt machen? Und sollte ich alle meine Methoden schreibgeschützt vom Delegatentyp machen, damit meine Benutzer." kann jede Methode durch eine eigene Implementierung ersetzen? " Nein, tun Sie nichts davon, bis Sie Beweise dafür haben , dass ein Benutzer diese Fähigkeit tatsächlich benötigt! Verbringen Sie Ihre wertvolle Zeit damit, Funktionen zu entwerfen, zu testen und zu implementieren, die Benutzer tatsächlich wollen und brauchen, und zwar aus einer Position des Wissens heraus.

Eric Lippert
quelle
17
+100 für "Alle Klassen werden versiegelt und alle Methoden sind nicht virtuell, bis Sie einen realen, kundenorientierten Grund haben, eine Methode zu entsiegeln oder virtuell zu machen."
Tallseth
15
Ich stimme dieser Antwort zu, aber ich finde es lustig, dass Sie befürworten, dass Klassen sealedstandardmäßig sein sollten, was sie nicht sind.
Matthew
6
@PopCatalin Sie gehen davon aus, dass mehr virtuelle Inhalte Ihren Kunden schützen. Tatsächlich setzt es den Kunden Ihren Fehlern mehr aus .
Deworde
27
-1 Ich stimme auch nicht zu. Sie sollten die Erweiterbarkeit Ihrer Software nicht auf alle bekannten Anwendungsfälle Ihrer Klassen beschränken müssen. Versiegelte + nicht virtuelle Typen verhindern das Testen und die Erweiterbarkeit Ihrer Typen und der Software, die Ihre Typen verwendet. Sie können System.Web als einen der reibungslosesten und nicht testbaren HTTP-Stacks betrachten, die heute, über 10 Jahre später, im modernen Einsatz sind, und wir sind immer noch nicht ohne die konkreten ASP.NET-Typen und ihre nicht ersetzbaren Impls. Nach mehreren Versuchen mit neuen Web-Abstraktionen ist MS selbst nicht in der Lage, Funktionen zwischen ihren verschiedenen HTTP-Stacks zu teilen
Mythos
12
Ich denke, es ist wahrscheinlich sehr wichtig, die Unterscheidung in Bezug auf die 'angemessene' Antwort hier zwischen zu treffen. "fwks für andere" und "Code, der von ppl w access verwendet wird, um die Quelle später zu ändern". Nicht virtuell und versiegelt in der .NET-FWK (als Beispiel) wurde an Orten verwendet, an denen dies niemals hätte sein dürfen, und die Verbraucher dieser FWK sind immer noch mit diesen über 10 Jahre alten „Annahmen“ über „notwendige“ Erweiterbarkeitspunkte konfrontiert. Sie können nicht vorhersagen, wie ppl Ihre Klassen erweitern möchte / muss. Während "virt-by-default" möglicherweise zu weit entfernt ist, ist "IMO" standardmäßig eine ebenso schlechte Wahl.
Sbohlen
27

Meiner Meinung nach ist die derzeit akzeptierte Antwort unnötig dogmatisch.

Fakt ist, wenn Sie eine Methode nicht als markieren virtual, können andere ihr Verhalten nicht überschreiben, und wenn Sie eine Klasse als markieren, sealedkönnen andere nicht von der Klasse erben. Dies kann erhebliche Schmerzen verursachen. Ich weiß nicht, wie oft ich eine API zum Markieren von Klassen sealedoder zum Nichtmarkieren von Methoden verflucht habe , virtualnur weil sie meinen Anwendungsfall nicht vorweggenommen haben.

Theoretisch mag es der richtige Ansatz sein, nur überschreibende Methoden und das Erben von Klassen zuzulassen, die überschrieben und vererbt werden sollen. In der Praxis ist es jedoch unmöglich, jedes mögliche Szenario vorherzusehen, und es gibt wirklich keinen guten Grund, sich so zu verschließen.

  1. Wenn Sie keinen guten Grund haben, markieren Sie Klassen nicht als sealed.
  2. Wenn Ihre Bibliothek von anderen verwendet werden soll, versuchen Sie zumindest, die Hauptmethoden einer Klasse, die das Verhalten enthalten, als zu markieren virtual.

Eine Möglichkeit, den Aufruf zu tätigen, besteht darin, den Namen der Methode oder Eigenschaft zu überprüfen. Eine GetLength () -Methode in einer Liste macht genau das, was der Name impliziert, und lässt nicht viel Interpretation zu . Eine Änderung der Implementierung wäre wahrscheinlich nicht sehr transparent, so dass es virtualwahrscheinlich unnötig ist , sie zu markieren . Das Markieren der AddMethode als virtuell ist weitaus nützlicher, da jemand eine spezielle Liste erstellen könnte, die nur einige Objekte über die Add-Methode usw. akzeptiert. Ein weiteres Beispiel sind benutzerdefinierte Steuerelemente. Sie möchten die Hauptzeichnungsmethode festlegen, virtualdamit andere den Großteil des Verhaltens verwenden und nur das Aussehen ändern können, aber Sie würden die X- und Y-Eigenschaften wahrscheinlich nicht überschreiben.

Am Ende muss man diese Entscheidung oft nicht sofort treffen. In einem internen Projekt, in dem Sie den Code trotzdem leicht ändern können, würde ich mir über diese Dinge keine Sorgen machen. Wenn eine Methode überschrieben werden muss, können Sie sie in diesem Fall jederzeit virtuell gestalten. Im Gegenteil, wenn das Projekt eine API oder Bibliothek ist, die von anderen verwendet wird und nur langsam aktualisiert wird, lohnt es sich auf jeden Fall, darüber nachzudenken, welche Klassen und Methoden nützlich sein könnten. In diesem Fall denke ich, dass es besser ist, offen zu sein, als streng geschlossen zu sein.

Patrick Klug
quelle
23

Nein! Da Sie nicht wissen, wie Ihre Klasse vererbt wird, sollten Sie eine Methode nur so markieren, als virtualob Sie wissen , dass sie überschrieben werden soll.

John Saunders
quelle
5

Nein. Nur Methoden, die abgeleitete Klassen angeben sollen, sollten virtuell sein.

Virtual ist nicht mit final verbunden.

Um zu verhindern, dass eine virtuelle Methode in c # überschrieben wird, verwenden Sie sealed

public class MyClass
{
    public sealed override void MyFinalMethod() {...}
}
nunespascal
quelle
5
FYI, in Java, finalauf eine Methode bedeutet not virtual.
John Saunders
4

Wir können Gründe für / wieder beide Lager heraufbeschwören, aber das ist völlig nutzlos.

In Java gibt es Millionen unbeabsichtigter nicht endgültiger öffentlicher Methoden, aber wir hören nur sehr wenige Horrorgeschichten.

In C # gibt es Millionen versiegelter öffentlicher Methoden, und wir hören nur sehr wenige Horrorgeschichten.

Es ist also keine große Sache - die Notwendigkeit, eine öffentliche Methode außer Kraft zu setzen, ist selten, daher ist sie in beiden Fällen umstritten.


Dies erinnert mich an ein anderes Argument - ob eine lokale Variable standardmäßig endgültig sein soll. Es ist eine ziemlich gute Idee, aber wir können nicht übertreiben, wie wertvoll sie ist. Es gibt Milliarden lokaler Variablen, die endgültig sein könnten, aber nicht endgültig sind, aber es hat sich gezeigt, dass es sich um ein tatsächliches Problem handelt.

unwiderlegbar
quelle
1

Wenn Sie eine Methode virtuell machen, wird im Allgemeinen jeder Code verlangsamt, der sie aufrufen muss. Diese Verlangsamung ist unbedeutend, kann jedoch in einigen Fällen recht groß sein (unter anderem, weil nicht virtuelle Methodenaufrufe inline sein können, wodurch der Optimierer möglicherweise unnötige Vorgänge eliminieren kann). Es ist nicht immer möglich vorherzusagen, inwieweit virtuelle Aufrufe die Ausführungsgeschwindigkeit beeinflussen können, und es sollte generell ungültig sein, Dinge zu tun, die den Code langsamer machen, es sei denn, es gibt einen erkennbaren Vorteil dafür.

Der Leistungsvorteil, Methoden nicht virtuell zu machen, reicht wahrscheinlich in vielen Fällen aus, um zu rechtfertigen, dass Methoden standardmäßig nicht virtuell sind. Wenn Klassen jedoch so konzipiert sind, dass sie vererbt werden, sollten die meisten Methoden virtuell und nicht versiegelt sein. Die primäre Verwendung für nicht virtuelle oder versiegelte Methoden sollte als Wrapper für andere (möglicherweise geschützte) virtuelle Methoden erfolgen (Code, der das zugrunde liegende Verhalten ändern möchte, sollte den entsprechenden virtuellen und nicht den Wrapper überschreiben).

Es gibt häufig nicht leistungsbezogene Gründe für das Markieren von Klassen als sealedoder das Beschränken der Vererbung auf andere Klassen innerhalb der Assembly. Wenn eine Klasse extern vererbbar ist, werden unter anderem alle Mitglieder mit protectedGültigkeitsbereich effektiv zu ihrer öffentlichen API hinzugefügt, und Änderungen an ihrem Verhalten in der Basisklasse können abgeleitete Klassen beschädigen, die auf diesem Verhalten beruhen. Wenn andererseits eine Klasse vererbbar ist, virtualerhöht das Erstellen ihrer Methoden ihre Exposition nicht wirklich. Wenn überhaupt, kann dies die Abhängigkeit der abgeleiteten Klasse von den Interna der Basisklasse verringern, indem sie ihnen ermöglicht, Aspekte der Implementierung der Basisklasse, die in der abgeleiteten Klasse nicht mehr relevant sind, vollständig zu "begraben" [z. B. wenn die Mitglieder vonList<T>waren virtuell, eine abgeleitete Klasse, die sie alle überschrieb, konnte ein Array von Arrays verwenden, um Dinge zu speichern (um Probleme mit großen Objekthaufen zu vermeiden), und musste nicht versuchen, das verwendete private Array List<T>konsistent mit dem Array von zu halten Arrays.

Superkatze
quelle
1

Ja du solltest. Ich möchte eine andere Antwort als die meisten anderen Antworten beantworten. Dies ist ein Fehler in C #. Ein Defekt. Ein Fehler im Design. Sie können dies beim Vergleich mit Java sehen, wo alle Methoden "virtuell" sind, sofern nicht anders angegeben ("final"). Natürlich, wenn es eine Klasse "Rechteck" mit der Methode "Fläche" gibt und Sie eine eigene Klasse haben möchten, die ein "Rechteck" mit Rändern darstellt. Sie möchten die vorhandene Klasse mit all ihren Eigenschaften und Methoden nutzen und nur eine Eigenschaft mit dem Rand "margin" hinzufügen, die dem regulären Rechteckbereich einen gewissen Wert hinzufügt. Wenn die Bereichsmethode in Rectangle nicht als virtuell markiert ist, du bist verdammt. Bild bitte eine Methode, die ein Array von Rechtecken nimmt und die Summe der Fläche aller Rechtecke zurückgibt. Einige können regelmäßige Rechtecke sein, andere mit Rand. Lesen Sie nun die Antwort zurück, die als "korrekt" gekennzeichnet ist, und beschreiben Sie "Sicherheitsproblem" oder "Testen". Diese sind im Vergleich zur Übersteuerungsbehinderung bedeutungslos.

Ich bin nicht überrascht, dass andere mit "Nein" geantwortet haben. Ich bin überrascht, dass die Autoren von C # das nicht sehen konnten, als sie ihre Sprache auf Java basierten.

Elad Lavi
quelle
0

Nein, Sie sollten nicht alle Methoden als virtuell markieren. Sie sollten überlegen, wie Ihre Klasse vererbt werden könnte. Wenn die Klasse nicht vererbt werden soll, markieren Sie sie versiegelt und die Mitglieder sollten offensichtlich nicht virtuell sein. Wenn Ihre Klasse wahrscheinlich vererbt wird, sollten Sie die Fähigkeit maximieren, das Verhalten zu überschreiben. Verwenden Sie daher in solchen Klassen großzügig überall virtuell, es sei denn, Sie haben einen Grund, dies nicht zu tun.

Elliott Anderson
quelle