Warum benötigen wir das neue Schlüsselwort und warum wird das Standardverhalten ausgeblendet und nicht überschrieben?

81

Ich habe mir diesen Blog-Beitrag angesehen und folgende Fragen gestellt:

  • Warum brauchen wir das newSchlüsselwort, um nur anzugeben, dass eine Basisklassenmethode ausgeblendet wird. Ich meine, warum brauchen wir das? Wenn wir das overrideSchlüsselwort nicht verwenden, verstecken wir dann nicht die Basisklassenmethode?
  • Warum wird in C # standardmäßig ausgeblendet und nicht überschrieben? Warum haben die Designer das so umgesetzt?
Sandkasten
quelle
6
Eine etwas interessantere Frage (für mich) ist, warum das Verstecken überhaupt legal ist. Es ist normalerweise ein Fehler (daher die Compiler-Warnung), selten erwünscht und bestenfalls immer problematisch.
Konrad Rudolph

Antworten:

127

Gute Fragen. Lassen Sie mich sie noch einmal sagen.

Warum ist es legal, eine Methode überhaupt mit einer anderen Methode auszublenden?

Lassen Sie mich diese Frage mit einem Beispiel beantworten. Sie haben eine Schnittstelle von CLR v1:

interface IEnumerable
{
    IEnumerator GetEnumerator();
}

Super. Jetzt in CLR v2 haben Sie Generika und Sie denken "Mann, wenn wir nur Generika in v1 gehabt hätten, hätte ich dies zu einer generischen Schnittstelle gemacht. Aber ich habe es nicht getan. Ich sollte jetzt etwas damit kompatibles machen, das generisch ist, damit Ich bekomme die Vorteile von Generika, ohne die Abwärtskompatibilität mit Code zu verlieren, der IEnumerable erwartet. "

interface IEnumerable<T> : IEnumerable
{
    IEnumerator<T> .... uh oh

Wie werden Sie die GetEnumerator-Methode aufrufen IEnumerable<T>? Denken Sie daran, dass GetEnumerator auf der nicht generischen Basisschnittstelle ausgeblendet werden soll. Sie möchten niemals, dass dieses Ding aufgerufen wird, es sei denn, Sie befinden sich explizit in einer abwärtskompatiblen Situation.

Das allein rechtfertigt das Verstecken von Methoden. Weitere Gedanken zu Begründungen des Versteckens von Methoden finden Sie in meinem Artikel zu diesem Thema .

Warum verursacht das Verstecken ohne "neu" eine Warnung?

Weil wir Sie darauf aufmerksam machen möchten, dass Sie etwas verstecken und es möglicherweise versehentlich tun. Denken Sie daran, dass Sie möglicherweise versehentlich etwas verstecken, weil die Basisklasse von einer anderen Person bearbeitet wurde, anstatt dass Sie Ihre abgeleitete Klasse bearbeitet haben.

Warum ist das Verstecken ohne "neu" eher eine Warnung als ein Fehler?

Gleicher Grund. Möglicherweise verstecken Sie versehentlich etwas, weil Sie gerade eine neue Version einer Basisklasse ausgewählt haben. Das passiert die ganze Zeit. FooCorp erstellt eine Basisklasse B. BarCorp erstellt eine abgeleitete Klasse D mit einer Methodenleiste, da ihre Kunden diese Methode mögen. FooCorp sieht das und sagt, hey, das ist eine gute Idee, wir können diese Funktionalität auf die Basisklasse übertragen. Sie tun dies und liefern eine neue Version von Foo.DLL aus. Wenn BarCorp die neue Version aufnimmt, wäre es schön, wenn ihnen mitgeteilt würde, dass ihre Methode jetzt die Basisklassenmethode verbirgt.

Wir möchten, dass diese Situation eine Warnung und kein Fehler ist, da dies bedeutet, dass dies eine andere Form des Problems der spröden Basisklasse ist . C # wurde sorgfältig entwickelt, damit bei einer Änderung an einer Basisklasse die Auswirkungen auf Code, der eine abgeleitete Klasse verwendet, minimiert werden.

Warum wird die Standardeinstellung ausgeblendet und nicht überschrieben?

Weil virtuelles Überschreiben gefährlich ist . Durch die virtuelle Überschreibung können abgeleitete Klassen das Verhalten von Code ändern, der für die Verwendung von Basisklassen kompiliert wurde. Etwas Gefährliches wie eine Außerkraftsetzung zu tun, sollte etwas sein, das Sie bewusst und absichtlich tun , nicht zufällig.

Eric Lippert
quelle
1
FWIW, ich bin nicht der Meinung, dass "[t] hat allein das Verstecken von Methoden rechtfertigt". Manchmal ist es besser, die Abwärtskompatibilität zu brechen. Python 3 hat gezeigt, dass dies tatsächlich funktioniert, ohne zu viel Arbeit zu zerstören. Es ist wichtig zu beachten, dass "Abwärtskompatibilität um jeden Preis" nicht die einzig vernünftige Option ist. Und dieser Grund überträgt sich auf den anderen Punkt: Das Ändern einer Basisklasse wird immer spröde sein. Das Brechen eines abhängigen Builds ist möglicherweise vorzuziehen. (Aber +1 für die Beantwortung der Frage aus meinem Kommentar unter der eigentlichen Frage.)
Konrad Rudolph
@Konrad: Ich bin damit einverstanden, dass Rückwärtskompatibilität schaden kann. Mono hat beschlossen, die Unterstützung für 1.1 in zukünftigen Versionen einzustellen.
Simendsjo
1
@Konrad: Das Aufheben der Abwärtskompatibilität mit Ihrer Sprache unterscheidet sich stark davon, Menschen, die Ihre Sprache verwenden, das Aufheben der Abwärtskompatibilität mit ihrem eigenen Code zu erleichtern. Erics Beispiel ist der 2. Fall. Und die Person, die diesen Code schreibt, hat eine Entscheidung getroffen, dass sie Abwärtskompatibilität wünscht. Die Sprache sollte diese Entscheidung nach Möglichkeit unterstützen.
Brian
Gott antworte, Eric. Es gibt jedoch einen genaueren Grund, warum das Überschreiben standardmäßig schlecht ist. Wenn eine Basisklasse Base eine neue Methode Duck () einführt (die ein Entenobjekt zurückgibt), die zufällig dieselbe Signatur hat wie Ihre vorhandene Methode Duck () (die Mario duck macht), in einer abgeleiteten Klasse abgeleitet (dh keine Kommunikation zwischen Sie und der Autor von Base) möchten nicht, dass Kunden beider Klassen Mario dazu bringen, sich zu ducken, wenn sie nur eine Ente wollen.
Jyoungdev
Zusammenfassend hat es hauptsächlich mit dem Ändern von Basisklassen und Schnittstellen zu tun.
Jyoungdev
15

Wenn der Methode in der abgeleiteten Klasse das neue Schlüsselwort vorangestellt wird, wird die Methode als unabhängig von der Methode in der Basisklasse definiert

Wenn Sie jedoch weder neu noch Überschreibungen angeben, ist die resultierende Ausgabe dieselbe wie bei der Angabe von neu, es wird jedoch eine Compiler-Warnung angezeigt (da Sie möglicherweise nicht wissen, dass Sie eine Methode in der Basisklassenmethode ausblenden). oder Sie wollten es vielleicht überschreiben und haben nur vergessen, das Schlüsselwort anzugeben).

So können Sie Fehler vermeiden und explizit zeigen, was Sie tun möchten, und der Code ist besser lesbar, sodass Sie Ihren Code leicht verstehen können.

Inkognito
quelle
12

Es ist erwähnenswert, dass der einzige Effekt newin diesem Zusammenhang darin besteht, eine Warnung zu unterdrücken. Die Semantik ändert sich nicht.

Eine Antwort lautet also: Wir müssen newdem Compiler signalisieren, dass das Verstecken beabsichtigt ist, und die Warnung entfernen.

Die folgende Frage lautet: Wenn Sie eine Methode nicht überschreiben können / können, warum sollten Sie dann eine andere Methode mit demselben Namen einführen? Weil das Verstecken im Wesentlichen ein Namenskonflikt ist. Und Sie würden es natürlich in den meisten Fällen vermeiden.

Der einzige gute Grund, warum ich mir vorstellen kann, absichtlich zu verstecken, ist, wenn Ihnen ein Name von einer Schnittstelle aufgezwungen wird.

Henk Holterman
quelle
6

In C # sind Mitglieder standardmäßig versiegelt, was bedeutet, dass Sie sie nicht überschreiben können (es sei denn, sie sind mit den Schlüsselwörtern virtualoder gekennzeichnet abstract), und dies aus Leistungsgründen . Der neue Modifikator wird verwendet, um ein geerbtes Mitglied explizit auszublenden.

Darin Dimitrov
quelle
4
Aber wann möchten Sie den neuen Modifikator wirklich verwenden?
Simendsjo
1
Wenn Sie ein von einer Basisklasse geerbtes Mitglied explizit ausblenden möchten. Das Ausblenden eines geerbten Mitglieds bedeutet, dass die abgeleitete Version des Mitglieds die Basisklassenversion ersetzt.
Darin Dimitrov
3
Alle Methoden, die die Basisklasse verwenden, rufen jedoch weiterhin die Basisimplementierung auf, nicht die abgeleitete. Dies scheint mir eine gefährliche Situation zu sein.
Simendsjo
@simendsjo, @Darin Dimitrov: Es fällt mir auch schwer, über einen Anwendungsfall nachzudenken, in dem das neue Schlüsselwort wirklich benötigt wird. Es scheint mir, dass es immer bessere Wege gibt. Ich frage mich auch, ob das Ausblenden einer Basisklassenimplementierung kein Konstruktionsfehler ist, der leicht zu Fehlern führt.
Dirk Vollmar
2

Wenn das Überschreiben standardmäßig ohne Angabe des overrideSchlüsselworts war, können Sie versehentlich eine Methode Ihrer Basis überschreiben, nur aufgrund der Namensgleichheit.

Die Strategie des .Net-Compilers besteht darin, Warnungen auszugeben, wenn etwas schief gehen könnte, um sicher zu gehen. Wenn in diesem Fall das Überschreiben standardmäßig aktiviert ist, muss für jede Überschreibungsmethode eine Warnung angezeigt werden - etwa "Warnung: Überprüfen Sie, ob Sie dies wirklich möchten." überschreiben'.

František Žiačik
quelle
0

Meine Vermutung wäre hauptsächlich auf die Vererbung mehrerer Schnittstellen zurückzuführen. Bei Verwendung diskreter Schnittstellen ist es sehr wahrscheinlich, dass zwei unterschiedliche Schnittstellen dieselbe Methodensignatur verwenden. Wenn Sie die Verwendung des newSchlüsselworts zulassen, können Sie diese verschiedenen Implementierungen mit einer Klasse erstellen, anstatt zwei unterschiedliche Klassen erstellen zu müssen.

Aktualisiert ... Eric gab mir eine Idee, wie ich dieses Beispiel verbessern kann.

public interface IAction1
{
    int DoWork();
}
public interface IAction2
{
    string DoWork();
}

public class MyBase : IAction1
{
    public int DoWork() { return 0; }
}
public class MyClass : MyBase, IAction2
{
    public new string DoWork() { return "Hi"; }
}

class Program
{
    static void Main(string[] args)
    {
        var myClass = new MyClass();
        var ret0 = myClass.DoWork(); //Hi
        var ret1 = ((IAction1)myClass).DoWork(); //0
        var ret2 = ((IAction2)myClass).DoWork(); //Hi
        var ret3 = ((MyBase)myClass).DoWork(); //0
        var ret4 = ((MyClass)myClass).DoWork(); //Hi
    }
}
Matthew Whited
quelle
1
Wenn Sie mehrere Schnittstellen verwenden, können Sie die Schnittstellenmethoden explizit benennen, um Kollisionen zu vermeiden. Ich verstehe nicht, was Sie unter "unterschiedliche Implementierung für eine Klasse" verstehen. Beide haben dieselbe Signatur ...
simendsjo
Mit dem newSchlüsselwort können Sie ab diesem Zeitpunkt auch die Vererbungsbasis für alle untergeordneten Elemente ändern.
Matthew Whited
-1

Wie bereits erwähnt, ermöglicht das Ausblenden von Methoden / Eigenschaften das Ändern von Dingen an einer Methode oder Eigenschaft, die ansonsten nicht ohne weiteres geändert werden könnten. Eine Situation, in der dies nützlich sein kann, besteht darin, einer geerbten Klasse Lese- / Schreibeigenschaften zuzuweisen, die in der Basisklasse schreibgeschützt sind. Angenommen, eine Basisklasse verfügt über eine Reihe von schreibgeschützten Eigenschaften namens Value1-Value40 (natürlich würde eine echte Klasse bessere Namen verwenden). Ein versiegelter Nachkomme dieser Klasse hat einen Konstruktor, der ein Objekt der Basisklasse nimmt und die Werte von dort kopiert. Die Klasse erlaubt nicht, dass sie danach geändert werden. Ein anderer, vererbbarer Nachkomme deklariert eine Lese- / Schreibeigenschaft namens Value1-Value40, die sich beim Lesen genauso verhält wie die Basisklassenversionen, beim Schreiben jedoch das Schreiben der Werte ermöglicht.

Ein Ärger bei diesem Ansatz - vielleicht kann mir jemand helfen - ist, dass ich keine Möglichkeit kenne, eine bestimmte Eigenschaft innerhalb derselben Klasse zu beschatten und zu überschreiben. Erlaubt eine der CLR-Sprachen dies (ich verwende vb 2005)? Es wäre nützlich, wenn das Basisklassenobjekt und seine Eigenschaften abstrakt sein könnten, aber dies würde eine Zwischenklasse erfordern, um die Eigenschaften Value1 bis Value40 zu überschreiben, bevor eine untergeordnete Klasse sie beschatten könnte.

Superkatze
quelle