Warum behandelt C # Methoden im Gegensatz zu Java standardmäßig als nicht virtuelle Funktionen? Ist es eher ein Leistungsproblem als andere mögliche Ergebnisse?
Ich erinnere mich daran, einen Absatz von Anders Hejlsberg über einige Vorteile der bestehenden Architektur gelesen zu haben. Aber was ist mit Nebenwirkungen? Ist es wirklich ein guter Kompromiss, standardmäßig nicht virtuelle Methoden zu haben?
c#
java
.net
virtual-functions
Burcu Dogan
quelle
quelle
this
Referenz null ist. Sehen Sie hier für weitere Informationen.Antworten:
Klassen sollten so konzipiert sein, dass die Vererbung sie nutzen kann.
virtual
Standardmäßig Methoden zu haben bedeutet, dass jede Funktion in der Klasse ausgesteckt und durch eine andere ersetzt werden kann, was nicht wirklich gut ist. Viele Leute glauben sogar, dass Klassensealed
standardmäßig sein sollten.virtual
Methoden können auch geringfügige Auswirkungen auf die Leistung haben. Dies ist jedoch wahrscheinlich nicht der Hauptgrund.quelle
callvirt
durch einen einfachencall
im IL-Code ersetzt werden muss (oder noch weiter stromabwärts beim JITting). Java HotSpot macht dasselbe. Der Rest der Antwort ist genau richtig.Ich bin überrascht, dass es hier einen solchen Konsens zu geben scheint, dass nicht virtuell standardmäßig der richtige Weg ist, Dinge zu tun. Ich werde auf der anderen - ich denke pragmatischen - Seite des Zauns herunterkommen.
Die meisten Rechtfertigungen lesen sich für mich wie das alte Argument "Wenn wir Ihnen die Macht geben, könnten Sie sich selbst verletzen". Von Programmierern?!
Mir scheint, dass der Codierer, der nicht genug wusste (oder nicht genug Zeit hatte), um seine Bibliothek für Vererbung und / oder Erweiterbarkeit zu entwerfen, der Codierer ist, der genau die Bibliothek erstellt hat, die ich wahrscheinlich reparieren oder optimieren muss - genau die Bibliothek, in der die Fähigkeit zum Überschreiben am nützlichsten wäre.
Die Häufigkeit, mit der ich hässlichen, verzweifelten Workaround-Code schreiben musste (oder die Verwendung aufgeben und meine eigene alternative Lösung verwenden musste), weil ich nicht weit überschreiben kann, überwiegt bei weitem die Häufigkeit, mit der ich jemals gebissen wurde ( zB in Java) durch Überschreiben, wo der Designer es vielleicht nicht in Betracht gezogen hat.
Standardmäßig nicht virtuell macht mein Leben schwerer.
UPDATE: Es wurde [ganz richtig] darauf hingewiesen, dass ich die Frage nicht wirklich beantwortet habe. Also - und mit Entschuldigung, dass ich ziemlich spät bin ...
Ich wollte in der Lage sein, etwas Markiges zu schreiben wie "C # implementiert Methoden standardmäßig als nicht virtuell, weil eine schlechte Entscheidung getroffen wurde, die Programme höher bewertet als Programmierer". (Ich denke, das könnte aufgrund einiger anderer Antworten auf diese Frage gerechtfertigt sein - wie Leistung (vorzeitige Optimierung, irgendjemand?) Oder Gewährleistung des Verhaltens von Klassen.)
Mir ist jedoch klar, dass ich nur meine Meinung äußern würde und nicht die endgültige Antwort, die Stack Overflow wünscht. Sicherlich dachte ich, auf höchster Ebene lautet die endgültige (aber nicht hilfreiche) Antwort:
Sie sind standardmäßig nicht virtuell, da die Sprachdesigner eine Entscheidung treffen mussten und genau das haben sie gewählt.
Jetzt denke ich, den genauen Grund, warum sie diese Entscheidung getroffen haben, werden wir nie ... oh, warte! Das Protokoll eines Gesprächs!
Es scheint also, dass die Antworten und Kommentare hier zu den Gefahren des Überschreibens von APIs und der Notwendigkeit, explizit für die Vererbung zu entwerfen, auf dem richtigen Weg sind, aber alle einen wichtigen zeitlichen Aspekt vermissen: Anders 'Hauptanliegen war es, das Implizite einer Klasse oder API beizubehalten Vertrag über Versionen . Und ich denke, er ist eher besorgt darüber, dass sich die .Net / C # -Plattform unter Code ändern kann, als darüber, dass sich der Benutzercode über der Plattform ändert. (Und sein "pragmatischer" Standpunkt ist genau das Gegenteil von meinem, weil er von der anderen Seite schaut.)
(Aber hätten sie nicht einfach standardmäßig virtuell auswählen und dann "final" durch die Codebasis pfeffern können? Vielleicht ist das nicht ganz dasselbe ... und Anders ist eindeutig schlauer als ich, also werde ich es lügen lassen.)
quelle
virtual
... Visual Studio-Add-In gekennzeichnet werden können?Weil es zu leicht zu vergessen ist, dass eine Methode möglicherweise überschrieben wird und nicht dafür entworfen wurde. C # bringt Sie zum Nachdenken, bevor Sie es virtuell machen. Ich denke, das ist eine großartige Designentscheidung. Einige Leute (wie Jon Skeet) haben sogar gesagt, dass Klassen standardmäßig versiegelt werden sollten.
quelle
Um zusammenzufassen, was andere gesagt haben, gibt es einige Gründe:
1- In C # gibt es viele Dinge in Syntax und Semantik, die direkt aus C ++ stammen. Die Tatsache, dass Methoden in C ++ standardmäßig nicht virtuell waren, beeinflusste C #.
2- Standardmäßig ist es ein Leistungsproblem, wenn jede Methode virtuell ist, da bei jedem Methodenaufruf die virtuelle Tabelle des Objekts verwendet werden muss. Darüber hinaus schränkt dies die Fähigkeit des Just-In-Time-Compilers, Inline-Methoden und andere Arten der Optimierung durchzuführen, stark ein.
3- Vor allem, wenn Methoden standardmäßig nicht virtuell sind, können Sie das Verhalten Ihrer Klassen garantieren. Wenn sie standardmäßig virtuell sind, wie in Java, können Sie nicht einmal garantieren, dass eine einfache Getter-Methode wie beabsichtigt funktioniert, da sie überschrieben werden kann, um irgendetwas in einer abgeleiteten Klasse zu tun (natürlich können und sollten Sie das machen Methode und / oder das Klassenfinale).
Man könnte sich fragen, wie Zifre erwähnte, warum die C # -Sprache nicht einen Schritt weiter ging und Klassen standardmäßig versiegelte. Das ist Teil der gesamten Debatte über die Probleme der Vererbung von Implementierungen, was ein sehr interessantes Thema ist.
quelle
C # wird von C ++ (und mehr) beeinflusst. C ++ aktiviert standardmäßig nicht den dynamischen Versand (virtuelle Funktionen). Ein (gutes?) Argument dafür ist die Frage: "Wie oft implementieren Sie Klassen, die Mitglieder einer Klasse hiearchy sind?". Ein weiterer Grund, das dynamische Versenden standardmäßig nicht zu aktivieren, ist der Speicherbedarf. Eine Klasse ohne virtuellen Zeiger (vpointer), der auf eine virtuelle Tabelle zeigt , ist natürlich kleiner als die entsprechende Klasse mit aktivierter später Bindung.
Das Leistungsproblem ist nicht so einfach mit "Ja" oder "Nein" zu beantworten. Der Grund dafür ist die Just In Time (JIT) -Kompilierung, die eine Laufzeitoptimierung in C # darstellt.
Eine andere, ähnliche Frage zur " Geschwindigkeit virtueller Anrufe ".
quelle
Der einfache Grund sind neben den Leistungskosten auch die Konstruktions- und Wartungskosten. Eine virtuelle Methode hat im Vergleich zu einer nicht virtuellen Methode zusätzliche Kosten, da der Designer der Klasse planen muss, was passiert, wenn die Methode von einer anderen Klasse überschrieben wird. Dies hat große Auswirkungen, wenn Sie erwarten, dass eine bestimmte Methode den internen Status aktualisiert oder ein bestimmtes Verhalten aufweist. Sie müssen jetzt planen, was passiert, wenn eine abgeleitete Klasse dieses Verhalten ändert. In dieser Situation ist es viel schwieriger, zuverlässigen Code zu schreiben.
Mit einer nicht virtuellen Methode haben Sie die vollständige Kontrolle. Alles, was schief geht, ist die Schuld des ursprünglichen Autors. Der Code ist viel einfacher zu überlegen.
quelle
Wenn alle C # -Methoden virtuell wären, wäre das vtbl viel größer.
C # -Objekte verfügen nur über virtuelle Methoden, wenn für die Klasse virtuelle Methoden definiert sind. Es ist wahr, dass alle Objekte Typinformationen haben, die ein vtbl-Äquivalent enthalten. Wenn jedoch keine virtuellen Methoden definiert sind, sind nur die Basisobjektmethoden vorhanden.
@ Tom Hawtin: Es ist wahrscheinlich genauer zu sagen, dass C ++, C # und Java alle aus der C-Sprachfamilie stammen :)
quelle
Aus einem Perl-Hintergrund kommend, denke ich, hat C # das Schicksal jedes Entwicklers besiegelt, der das Verhalten einer Basisklasse durch eine nicht virtuelle Methode erweitern und ändern wollte, ohne alle Benutzer der neuen Klasse dazu zu zwingen, sich möglicherweise hinter den Kulissen bewusst zu werden Einzelheiten.
Betrachten Sie die List-Klasse 'Add-Methode. Was ist, wenn ein Entwickler eine von mehreren potenziellen Datenbanken aktualisieren möchte, wenn eine bestimmte Liste hinzugefügt wird? Wenn 'Hinzufügen' standardmäßig virtuell gewesen wäre, könnte der Entwickler eine 'BackedList'-Klasse entwickeln, die die' Add'-Methode überschreibt, ohne dass der gesamte Client-Code weiß, dass es sich um eine 'BackedList' anstelle einer regulären 'List' handelt. Für alle praktischen Zwecke kann die 'BackedList' als eine weitere 'Liste' aus dem Client-Code angesehen werden.
Dies ist aus Sicht einer großen Hauptklasse sinnvoll, die möglicherweise Zugriff auf eine oder mehrere Listenkomponenten bietet, die selbst von einem oder mehreren Schemas in einer Datenbank unterstützt werden. Da C # -Methoden standardmäßig nicht virtuell sind, kann die von der Hauptklasse bereitgestellte Liste keine einfache IEnumerable- oder ICollection- oder sogar List-Instanz sein, sondern muss dem Client stattdessen als 'BackedList' angekündigt werden, um die neue Version sicherzustellen der Operation 'Hinzufügen' wird aufgerufen, um das richtige Schema zu aktualisieren.
quelle
B
istA
. WennB
etwas anderes erforderlich ist,A
dann ist es nichtA
. Ich glaube, dass das Überschreiben als Fähigkeit in der Sprache selbst ein Designfehler ist. Wenn Sie eine andereAdd
Methode benötigen , ist Ihre Sammlungsklasse keineList
. Der Versuch, es zu sagen, ist eine Fälschung. Der richtige Ansatz ist hier die Komposition (und nicht die Fälschung). Das gesamte Framework basiert zwar auf übergeordneten Funktionen, aber ich mag es einfach nicht.Es ist sicherlich kein Leistungsproblem. Der Java-Interpreter von Sun verwendet denselben Code für den Versand (
invokevirtual
Bytecode), und HotSpot generiert unabhängig davon denselben Codefinal
. Ich glaube, dass alle C # -Objekte (aber keine Strukturen) virtuelle Methoden haben, daher benötigen Sie immer dievtbl
Identifikation / runtime class. C # ist ein Dialekt von "Java-ähnlichen Sprachen". Zu behaupten, dass es aus C ++ stammt, ist nicht ganz ehrlich.Es gibt eine Idee, dass Sie "für die Vererbung entwerfen oder sie verbieten" sollten. Das klingt nach einer großartigen Idee, bis Sie einen schwerwiegenden Geschäftsfall haben, den Sie schnell beheben können. Vielleicht erben Sie von Code, den Sie nicht kontrollieren.
quelle
final
und effektivfinal
Methoden ist trivial. Es ist auch erwähnenswert, dass es bimorphes Inlining ausführen kann, dh Inline-Methoden mit zwei verschiedenen Implementierungen.