Warum muss das Schlüsselwort überschreiben vor abstrakten Methoden stehen, wenn wir sie in einer untergeordneten Klasse implementieren?

9

Wenn wir eine Klasse erstellen, die von einer abstrakten Klasse erbt, und wenn wir die geerbte abstrakte Klasse implementieren, warum müssen wir das Schlüsselwort override verwenden?

public abstract class Person
{
    public Person()
    {

    }

    protected virtual void Greet()
    {
        // code
    }

    protected abstract void SayHello();
}

public class Employee : Person
{
    protected override void SayHello() // Why is override keyword necessary here?
    {
        throw new NotImplementedException();
    }

    protected override void Greet()
    {
        base.Greet();
    }
}

Da die Methode in ihrer übergeordneten Klasse als abstrakt deklariert ist, hat sie keine Implementierung in der übergeordneten Klasse. Warum ist hier die Schlüsselwortüberschreibung hier erforderlich?

psj01
quelle
2
Eine abstrakte Methode ist implizit eine virtuelle Methode gemäß Spezifikation
Pavel Anikhouski
"Der Überschreibungsmodifikator ist erforderlich, um die abstrakte oder virtuelle Implementierung einer geerbten Methode, Eigenschaft, eines Indexers oder eines Ereignisses zu erweitern oder zu ändern." docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
gunr2171
2
Denn wenn Sie es nicht dort ablegen, "verstecken" Sie die Methode, anstatt sie zu überschreiben. Aus diesem Grund erhalten Sie die Warnung, dass "Wenn dies beabsichtigt ist, verwenden Sie das newSchlüsselwort" ... Sie würden auch den Fehler "Keine Methode überschreibt Basisklassenmethode" erhalten.
Ron Beyer
@ RonBeyer mit einer virtuellen Methode ja, aber mit abstrakt würde es einfach nicht kompilieren.
Johnathan Barclay

Antworten:

14

Wenn wir eine Klasse erstellen, die von einer abstrakten Klasse erbt, und wenn wir die geerbte abstrakte Klasse implementieren, warum müssen wir das Schlüsselwort override verwenden?

"Warum?" Fragen wie diese können schwer zu beantworten sein, da sie vage sind. Ich gehe davon aus, dass Ihre Frage lautet: "Welche Argumente könnten während des Sprachdesigns vorgebracht werden, um für die Position zu argumentieren, dass das overrideSchlüsselwort erforderlich ist ?"

Beginnen wir mit einem Schritt zurück. In einigen Sprachen, beispielsweise Java, sind Methoden standardmäßig virtuell und werden automatisch überschrieben. Die Designer von C # waren sich dessen bewusst und betrachteten es als kleinen Fehler in Java. C # ist nicht "Java mit den dummen herausgenommenen Teilen", wie einige gesagt haben, aber die Designer von C # wollten unbedingt aus den problematischen Designpunkten von C, C ++ und Java lernen und sie nicht in C # replizieren.

Die C # -Designer betrachteten das Überschreiben als mögliche Fehlerquelle. Schließlich ist es eine Möglichkeit, das Verhalten von vorhandenem, getestetem Code zu ändern , und das ist gefährlich. Überschreiben sollte nicht beiläufig oder versehentlich erfolgen. Es sollte von jemandem entworfen werden, der darüber nachdenkt . Aus diesem Grund sind Methoden standardmäßig nicht virtuell und Sie müssen angeben, dass Sie eine Methode überschreiben.

Das ist die grundlegende Argumentation. Wir können jetzt auf einige fortgeschrittenere Überlegungen eingehen.

Die Antwort von StriplingWarrior bietet einen guten ersten Anhaltspunkt für ein fortgeschritteneres Argument. Der Autor der abgeleiteten Klasse ist möglicherweise nicht über die Basisklasse informiert, beabsichtigt möglicherweise, eine neue Methode zu erstellen, und wir sollten nicht zulassen, dass der Benutzer versehentlich überschreibt .

Obwohl dieser Punkt vernünftig ist, gibt es eine Reihe von Gegenargumenten, wie zum Beispiel:

  • Der Autor einer abgeleiteten Klasse hat die Verantwortung, alles über die Basisklasse zu wissen ! Sie verwenden diesen Code erneut und sollten die erforderliche Sorgfalt walten lassen, um diesen Code gründlich zu verstehen, bevor sie ihn erneut verwenden.
  • In Ihrem speziellen Szenario ist die virtuelle Methode abstrakt. Es wäre ein Fehler, ihn nicht zu überschreiben, und daher ist es unwahrscheinlich, dass der Autor versehentlich eine Implementierung erstellt.

Lassen Sie uns dann ein noch weiter fortgeschrittenes Argument zu diesem Punkt vorbringen. Unter welchen Umständen kann der Autor einer abgeleiteten Klasse dafür entschuldigt werden, dass er nicht weiß, was die Basisklasse tut? Betrachten Sie dieses Szenario:

  • Der Autor der Basisklasse erstellt eine abstrakte Basisklasse B.
  • Der abgeleitete Klassenautor in einem anderen Team erstellt mit Methode M eine abgeleitete Klasse D.
  • Der Autor der Basisklasse erkennt, dass Teams, die die Basisklasse B erweitern, immer eine Methode M angeben müssen, sodass der Autor der Basisklasse die abstrakte Methode M hinzufügt.
  • Was passiert, wenn Klasse D neu kompiliert wird?

Wir möchten, dass der Autor von D darüber informiert wird, dass sich etwas Relevantes geändert hat . Das Relevante, was sich geändert hat, ist, dass M jetzt eine Anforderung ist und dass ihre Implementierung überlastet werden muss. DM muss möglicherweise sein Verhalten ändern, sobald wir wissen, dass es von der Basisklasse aufgerufen werden kann. Das Richtige ist, nicht still zu sagen "Oh, DM existiert und erweitert BM". Das Richtige für den Compiler ist, fehlzuschlagen und zu sagen: "Hey, Autor von D, überprüfen Sie diese Annahme, die nicht mehr gültig ist, und korrigieren Sie gegebenenfalls Ihren Code."

In Ihrem Beispiel : Angenommen , das overridewar optional auf , SayHelloweil es eine abstrakte Methode überschreibt. Es gibt zwei Möglichkeiten: (1) Der Autor des Codes beabsichtigt, eine abstrakte Methode zu überschreiben, oder (2) die überschreibende Methode überschreibt versehentlich, weil jemand anderes die Basisklasse geändert hat und der Code jetzt auf subtile Weise falsch ist. Wir können diese Möglichkeiten nicht auseinanderhalten, wenn dies overrideoptional ist .

Wenn overridees jedoch erforderlich ist, können wir drei Szenarien unterscheiden. Wenn es einen möglichen Fehler im Code overridegibt, fehlt dieser . Wenn es absichtlich überschrieben wird, overrideist es vorhanden . Und wenn es absichtlich nicht überschrieben wird, dann newist es vorhanden . Das Design von C # ermöglicht es uns, diese subtilen Unterscheidungen zu treffen.

Denken Sie daran , dass das Melden von Compilerfehlern das Lesen der Gedanken des Entwicklers erfordert . Der Compiler muss aus falschem Code ableiten , welchen korrekten Code der Autor wahrscheinlich im Sinn hatte , und einen Fehler angeben, der ihn in die richtige Richtung weist. Je mehr Hinweise wir den Entwickler dazu bringen können, im Code zu hinterlassen, was er gedacht hat, desto besser kann der Compiler Fehler melden und desto schneller können Sie Ihre Fehler finden und beheben.

Im Allgemeinen wurde C # jedoch für eine Welt entwickelt, in der sich der Code ändert . Viele Funktionen von C #, die "ungerade" erscheinen, sind tatsächlich vorhanden, weil sie den Entwickler informieren, wenn eine früher gültige Annahme ungültig geworden ist, weil sich eine Basisklasse geändert hat. Diese Fehlerklasse wird als "spröde Basisklassenfehler" bezeichnet, und C # bietet eine Reihe interessanter Abhilfemaßnahmen für diese Fehlerklasse.

Eric Lippert
quelle
4
Vielen Dank für die Ausarbeitung. Ich freue mich immer über Ihre Antworten, sowohl weil es gut ist, eine maßgebliche Stimme von jemandem "von innen" zu haben, als auch weil Sie die Dinge einfach und vollständig erklären.
StriplingWarrior
Vielen Dank für die ausführliche Erklärung! Schätze es wirklich.
psj01
Tolle Punkte! Könnten Sie einige der anderen von Ihnen erwähnten Abhilfemaßnahmen auflisten?
aksh1618
3

Hiermit wird angegeben, ob Sie versuchen, eine andere Methode in der übergeordneten Klasse zu überschreiben oder eine neue Implementierung zu erstellen, die für diese Ebene der Klassenhierarchie eindeutig ist. Es ist denkbar, dass ein Programmierer die Existenz einer Methode in einer übergeordneten Klasse mit genau derselben Signatur wie die in seiner Klasse erstellte nicht kennt, was zu einigen bösen Überraschungen führen kann.

Während es stimmt, dass eine abstrakte Methode in einer nicht abstrakten untergeordneten Klasse überschrieben werden muss , hielten es die Handwerker von C # wahrscheinlich immer noch für besser, explizit zu sagen, was Sie versuchen zu tun.

StriplingWarrior
quelle
Dies ist ein guter Anfang, um die Argumentation des Sprachdesign-Teams zu verstehen. Ich habe eine Antwort hinzugefügt, die zeigt, wie das Team mit der hier geäußerten Idee beginnt, aber noch einen Schritt weiter geht.
Eric Lippert
1

Da abstractMethode ist eine virtuelle Methode ohne Implementierung, pro Sprache C # Spezifikation , eine Einrichtung , die abstrakte Methode implizit eine virtuelle Methode ist. Und overridewird verwendet, um die abstrakte oder virtuelle Implementierung zu erweitern oder zu ändern, wie Sie hier sehen können

Um es ein wenig umzuformulieren: Sie verwenden virtuelle Methoden, um eine Art späte Bindung zu implementieren, während abstrakte Methoden die Unterklassen des Typs zwingen, die Methode explizit zu überschreiben. Das ist der Punkt, wenn die Methode ist virtual, kann sie überschrieben werden, wenn es eine ist abstract- es muss überschrieben werden

Pavel Anikhouski
quelle
1
Ich denke, die Frage bezieht sich nicht darauf, was es tut, sondern warum es explizit sein muss.
Johnathan Barclay
0

Um die Antwort von @ StriplingWarrior zu ergänzen, wurde meiner Meinung nach auch eine Syntax verwendet, die mit dem Überschreiben einer virtuellen Methode in der Basisklasse übereinstimmt.

public abstract class MyBase
{
    public virtual void MyVirtualMethod() { }

    public virtual void MyOtherVirtualMethod() { }

    public abstract void MyAbtractMethod();
}

public class MyDerived : MyBase
{
    // When overriding a virtual method in MyBase, we use the override keyword.
    public override void MyVirtualMethod() { }

    // If we want to hide the virtual method in MyBase, we use the new keyword.
    public new void MyOtherVirtualMethod() { }

    // Because MyAbtractMethod is abstract in MyBase, we have to override it: 
    // we can't hide it with new.
    // For consistency with overriding a virtual method, we also use the override keyword.
    public override void MyAbtractMethod() { }
}

C # hätte also so entworfen werden können, dass Sie das Schlüsselwort override nicht zum Überschreiben abstrakter Methoden benötigen, aber ich denke, die Designer haben entschieden, dass dies verwirrend wäre, da es nicht mit dem Überschreiben einer virtuellen Methode vereinbar wäre.

Polyfun
quelle
Betreff: "Die Designer haben entschieden, dass dies verwirrend ist, da es nicht mit dem Überschreiben einer virtuellen Methode vereinbar ist" - ja, aber mehr. Angenommen, Sie haben eine Basisklasse B mit einer virtuellen Methode M und eine abgeleitete Klasse D mit einer Überschreibung. Angenommen, der Autor von B beschließt, M abstrakt zu machen. Das ist eine bahnbrechende Veränderung, aber vielleicht tun sie es auch. Frage: Sollte der Autor von D aufgefordert werden, das zu entfernen override? Ich denke, die meisten Leute würden zustimmen, dass es absurd ist, den Autor von D zu einer unnötigen Codeänderung zu zwingen; Ihre Klasse ist in Ordnung!
Eric Lippert