Typescript: Unterstützen Sie das Überschreiben des Schlüsselworts für Klassenmethoden

Erstellt am 10. Feb. 2015  ·  210Kommentare  ·  Quelle: microsoft/TypeScript

(Aktualisierung von @RyanCavanuagh)

Bitte sehen Sie sich diesen Kommentar an, bevor Sie nach „Aktualisierungen“, „Bitte fügen Sie dies jetzt hinzu“ usw. fragen. Kommentare, die nicht sinnvoll zur Diskussion beitragen, werden entfernt, um die Länge des Threads angemessen zu halten.


(HINWEIS, dies ist _kein_ Duplikat von Ausgabe Nr. 1524. Der Vorschlag hier entspricht eher dem C++-Override-Spezifizierer, der für Typoskript viel sinnvoller ist.)

Ein Override-Schlüsselwort wäre im Typoskript sehr nützlich. Es wäre ein optionales Schlüsselwort für jede Methode, die eine Superklassenmethode überschreibt, und würde ähnlich wie der Override-Bezeichner in C++ eine Absicht anzeigen, dass „_der Name+Signatur dieser Methode immer mit dem Namen+Signatur einer Superklassenmethode übereinstimmen sollte_“ . Dies fängt eine ganze Reihe von Problemen in größeren Codebasen ab, die sonst leicht übersehen werden können.

Wieder ähnlich wie bei C++, _es ist kein Fehler, das Schlüsselwort override bei einer überschriebenen Methode wegzulassen_. In diesem Fall verhält sich der Compiler genau so, wie er es derzeit tut, und überspringt die zusätzlichen Überprüfungen der Kompilierzeit, die mit dem Schlüsselwort override verbunden sind. Dies ermöglicht komplexere nicht typisierte JavaScript-Szenarien, bei denen die Überschreibung der abgeleiteten Klasse nicht genau mit der Signatur der Basisklasse übereinstimmt.

Beispielanwendung

class Animal {
    move(meters:number):void {
    }
}

class Snake extends Animal {
    override move(meters:number):void {
    }
}

Beispiele für Fehlerbedingungen beim Kompilieren

// Add an additional param to move, unaware that the intent was 
// to override a specific signature in the base class
class Snake extends Animal {
    override move(meters:number, height:number):void {
    }
}
// COMPILE ERROR: Snake super does not define move(meters:number,height:number):void

// Rename the function in the base class, unaware that a derived class
// existed that was overriding the same method and hence it needs renaming as well
class Animal {
    megamove(meters:number):void {
    }
}
// COMPILE ERROR: Snake super does not define move(meters:number):void

// Require the function to now return a bool, unaware that a derived class
// existed that was still using void, and hence it needs updating
class Animal {
    move(meters:number):bool {
    }
}
// COMPILE ERROR: Snake super does not define move(meters:number):void

IntelliSense

Neben einer zusätzlichen Kompilierzeitvalidierung bietet das Schlüsselwort override einen Mechanismus für Typoskript-Intellisense, um verfügbare Supermethoden einfach anzuzeigen und auszuwählen, wobei die Absicht darin besteht, eine davon in einer abgeleiteten Klasse gezielt zu überschreiben. Derzeit ist dies sehr umständlich und beinhaltet das Durchsuchen der Superklassenkette, das Suchen der Methode, die Sie überschreiben möchten, und das anschließende Kopieren und Einfügen in die abgeleitete Klasse, um sicherzustellen, dass die Signaturen übereinstimmen.

Vorgeschlagener IntelliSense-Mechanismus

Innerhalb einer Klassendeklaration:

  1. Typ überschreiben
  2. Ein Dropdown-Menü zur automatischen Vervollständigung aller Supermethoden für die Klasse wird angezeigt
  3. In der Dropdown-Liste wird eine Methode ausgewählt
  4. Die Signatur für die Methode wird nach dem Schlüsselwort override in die Klassendeklaration ausgegeben.
Add a Flag Revisit Suggestion

Hilfreichster Kommentar

Bitte entschuldigen Sie das Jammern, aber ehrlich gesagt, während Ihr Argument auf das Schlüsselwort public in einer Sprache zutrifft, in der alles standardmäßig öffentlich ist, in der Tat ziemlich weltspaltend, mit Dingen wie abstract und einem optionalen override Das Schlüsselwort

Das Überschreiben ist einer der wenigen verbliebenen äußerst tippfehlerempfindlichen Aspekte der Sprache, da ein falsch geschriebener überschreibender Methodenname kein offensichtliches Problem bei der Kompilierzeit darstellt. Der Vorteil von override liegt auf der Hand, da Sie damit Ihre Absicht zum Überschreiben angeben können - wenn die Basismethode nicht existiert, ist dies ein Kompilierzeitfehler. Alle begrüßen das Typensystem. Warum sollte das niemand wollen?

Alle 210 Kommentare

In der Tat ein guter Vorschlag.

Was ist jedoch mit den folgenden Beispielen, die für mich gültige Überschreibungen sind:

class Snake extends Animal {
    override move(meters:number, height=-1):void {
    }
}
class A {...}
class Animal {
    setA(a: A): void {...}
    getA(): A {...}
}

class B extends A {...}
class Snake extends Animal {
    override setA(a: B): void {...}
    override getA(): B {...}
}

Zusätzlich würde ich ein Compiler-Flag hinzufügen, um zu erzwingen, dass das Schlüsselwort override vorhanden ist (oder als Warnung gemeldet wird).
Der Grund ist, beim Umbenennen einer Methode in einer Basisklasse abzufangen, die von geerbten Klassen bereits implementiert wird (aber nicht als Überschreibung gedacht ist).

Ah schöne Beispiele. Im Allgemeinen würde ich erwarten, dass die Verwendung des Schlüsselworts override einen _exakten_ Abgleich von Signaturen erzwingt, da das Ziel seiner Verwendung darin besteht, eine strikte typisierte Klassenhierarchie aufrechtzuerhalten. Also um deine Beispiele anzusprechen:

  1. Hinzufügen eines zusätzlichen Standardparameters. Dies würde einen Kompilierungsfehler erzeugen: Snake Super definiert nicht move(meters:number):void. Während die abgeleitete Methode funktional konsistent ist, erwartet der Clientcode, der Animal.move aufruft, möglicherweise nicht, dass abgeleitete Klassen auch die Höhe berücksichtigen (da die Basis-API dies nicht verfügbar macht).
  2. Dies würde (und sollte immer) einen Kompilierungsfehler erzeugen, da es nicht funktional konsistent ist. Betrachten Sie die folgende Ergänzung zum Beispiel:
class C extends A {...}
var animal : Animal = new Snake();
animal.setA(new C());
// This will have undefined run-time behavior, as C will be interpreted as type B in Snake.setA

Beispiel (2.) ist also eine großartige Demonstration dafür, wie ein Override-Schlüsselwort subtile Grenzfälle zur Kompilierzeit abfangen kann, die sonst übersehen würden! :)

Und ich möchte noch einmal betonen, dass beide Beispiele in bestimmten kontrollierten/erweiterten JavaScript-Szenarien gültig sein können, die möglicherweise erforderlich sind ... in diesem Fall können Benutzer einfach das Schlüsselwort override weglassen.

Dies wird nützlich sein. Wir umgehen dies derzeit, indem wir einen Dummy-Verweis auf die Super-Methode einfügen:

class Snake extends Animal {
    move(meters:number, height?:number):void {
         super.move; // override fix
    }
}

Aber das schützt nur vor dem zweiten Fall: Super-Methoden werden umbenannt. Signaturänderungen lösen keinen Übersetzungsfehler aus. Darüber hinaus ist dies eindeutig ein Hack.

Ich denke auch nicht, dass Standard- und optionale Parameter in der Signatur der abgeleiteten Klassenmethode einen Kompilierungsfehler auslösen sollten. Das mag richtig sein, widerspricht aber der inhärenten Flexibilität von JavaScript.

@rwyborn
Es scheint, dass wir nicht dasselbe Verhalten erwarten.
Sie würden dieses Override-Schlüsselwort verwenden, um eine gleiche Signatur sicherzustellen, während ich es eher als Lesbarkeitsoption verwenden würde (also meine Bitte, eine Compiler-Option hinzuzufügen, um ihre Verwendung zu erzwingen).
Tatsächlich würde ich wirklich erwarten, dass TS ungültige Überschreibungsmethoden erkennt (auch ohne die Verwendung von Override).
Typischerweise:

class Snake extends Animal {
    move(meters:number, height:number):void {}
}

sollte einen Fehler auslösen, da es sich tatsächlich um eine Überschreibung von Animal.move() (JS-Verhalten) handelt, aber um eine inkompatible (weil die Höhe nicht optional sein soll, während sie undefiniert ist, wenn sie von einer Animal-"Referenz" aufgerufen wird).
Tatsächlich würde die Verwendung von override nur (durch den Compiler) bestätigen, dass diese Methode wirklich in der Basisklasse vorhanden ist (und damit mit einer konformen Signatur, aber aufgrund des vorherigen Punkts, nicht aufgrund des Schlüsselworts override).

@stephanedr , als Einzelbenutzer stimme ich Ihnen tatsächlich zu, dass der Compiler die Signatur immer bestätigen sollte, da ich persönlich gerne eine strikte Typisierung innerhalb meiner Klassenhierarchien erzwinge (auch wenn Javascript dies nicht tut !!).

Wenn ich jedoch vorschlage, dass dieses Verhalten über das Schlüsselwort override optional ist, versuche ich zu bedenken, dass Javascript letztendlich nicht typisiert ist und daher das Erzwingen des strikten Signaturabgleichs standardmäßig dazu führen würde, dass einige Javascript-Entwurfsmuster nicht mehr in Typescript ausgedrückt werden können.

@rwyborn Ich bin froh, dass Sie die C++-Implementierung erwähnt haben, denn genau so habe ich mir vorgestellt, dass es funktionieren sollte, bevor ich hierher kam - optional. Allerdings würde ein Compiler-Flag, das die Verwendung des Schlüsselworts override erzwingt, in meinem Buch gut ankommen.

Das Schlüsselwort würde Kompilierzeitfehler für ungeschickte Eingaben eines Entwicklers zulassen, was mich am meisten an Überschreibungen in ihrer aktuellen Form beunruhigt.

class Base {
    protected commitState() : void {

    }
}


class Implementation extends Base {
    override protected comitState() : void {   /// error - 'comitState' doesn't exist on base type

    }
}

Derzeit (Stand 1.4) würde die obige Klasse Implementation nur eine neue Methode deklarieren und der Entwickler wäre nicht klüger, bis er bemerkt, dass sein Code nicht funktioniert.

Bei der Vorschlagsprüfung besprochen.

Wir verstehen die Anwendungsfälle hier definitiv. Das Problem ist, dass das Hinzufügen zu diesem Zeitpunkt in der Sprache mehr Verwirrung stiftet als beseitigt. Eine Klasse mit 5 Methoden, von denen 3 mit override gekennzeichnet sind, würde nicht implizieren, dass die anderen 2 _nicht_ außer Kraft gesetzt werden. Um seine Existenz zu rechtfertigen, müsste der Modifikator die Welt wirklich sauberer aufteilen.

Bitte entschuldigen Sie das Jammern, aber ehrlich gesagt, während Ihr Argument auf das Schlüsselwort public in einer Sprache zutrifft, in der alles standardmäßig öffentlich ist, in der Tat ziemlich weltspaltend, mit Dingen wie abstract und einem optionalen override Das Schlüsselwort

Das Überschreiben ist einer der wenigen verbliebenen äußerst tippfehlerempfindlichen Aspekte der Sprache, da ein falsch geschriebener überschreibender Methodenname kein offensichtliches Problem bei der Kompilierzeit darstellt. Der Vorteil von override liegt auf der Hand, da Sie damit Ihre Absicht zum Überschreiben angeben können - wenn die Basismethode nicht existiert, ist dies ein Kompilierzeitfehler. Alle begrüßen das Typensystem. Warum sollte das niemand wollen?

Ich stimme zu 100 % mit @hdachev überein, die kleine Inkonsistenz, auf die auch von @RyanCavanaugh verwiesen wird, wird leicht durch die Vorteile des Schlüsselworts bei der Einführung von Überprüfungen der Kompilierzeit in Methodenüberschreibungen aufgewogen. Ich möchte noch einmal darauf hinweisen, dass C++ ein optionales Override-Schlüsselwort erfolgreich auf genau die gleiche Weise verwendet, wie es für Typoskript vorgeschlagen wird.

Ich kann nicht genug betonen, welchen Unterschied die Überschreibungsprüfung in einer großen Codebasis mit komplexen OO-Bäumen macht.

Abschließend möchte ich hinzufügen, dass, wenn die Inkonsistenz eines optionalen Schlüsselworts wirklich ein Problem darstellt, der C#-Ansatz verwendet werden könnte, d. h. die obligatorische Verwendung der Schlüsselwörter „new“ oder „override“:

class Dervied extends Base {

    new FuncA(newParam) {} // "new" says that I am implementing a new version of FuncA() with a different signature to the base class version

    override FuncB() {} // "override" says that I am implementing exactly the same signature as the base class version

    FuncC() {} // If FuncC exists in the base class then this is a compile error. I must either use the override keyword (I am matching the signature) or the new keyword (I am changing the signature)
}

Dies ist nicht analog zu public , da eine Eigenschaft ohne Zugriffsmodifikator als öffentlich bekannt ist; Eine Methode ohne override ist _nicht_ dafür bekannt, nicht überschrieben zu werden.

Hier ist eine laufzeitgeprüfte Lösung mit Decorators (kommt in TS1.5), die gute Fehlermeldungen mit sehr wenig Overhead erzeugt:

/* Put this in a helper library somewhere */
function override(container, key, other1) {
    var baseType = Object.getPrototypeOf(container);
    if(typeof baseType[key] !== 'function') {
        throw new Error('Method ' + key + ' of ' + container.constructor.name + ' does not override any base class method');
    }
}

/* User code */
class Base {
    public baseMethod() {
        console.log('Base says hello');
    }
}

class Derived extends Base {
    // Works
    <strong i="9">@override</strong>
    public baseMethod() {
        console.log('Derived says hello');
    }

    // Causes exception
    <strong i="10">@override</strong>
    public notAnOverride() {
        console.log('hello world');
    }
}

Das Ausführen dieses Codes erzeugt einen Fehler:

Fehler: Die Methode notAnOverride von Derived überschreibt keine Basisklassenmethode

Da dieser Code zum Zeitpunkt der Klasseninitialisierung ausgeführt wird, benötigen Sie nicht einmal Komponententests, die für die betreffenden Methoden spezifisch sind. Der Fehler tritt auf, sobald Ihr Code geladen wird. Sie können auch eine "schnelle" Version von override abonnieren, die keine Überprüfung auf Produktionsbereitstellungen durchführt.

@RyanCavanaugh Wir sind also bei Typescript 1.6 und Dekorateure sind immer noch eine experimentelle Funktion, nicht etwas, das ich in einer großen Produktionscodebasis als Hack einsetzen möchte, um das Überschreiben zum Laufen zu bringen.

Um noch einen anderen Blickwinkel auszuprobieren, unterstützt jede beliebte typisierte Sprache da draußen das Schlüsselwort "override". Swift, ActionScript, C#, C++ und F#, um nur einige zu nennen. Alle diese Sprachen teilen die kleineren Probleme, die Sie in diesem Thread zum Überschreiben geäußert haben, aber es gibt eindeutig eine große Gruppe da draußen, die sieht, dass die Vorteile des Überschreibens diese kleinen Probleme bei weitem überwiegen.

Sind Ihre Einwände rein auf Kosten-Nutzen-Basis? Wenn ich das tatsächlich in einer PR umsetzen würde, würde es akzeptiert werden?

Es ist nicht nur eine Kosten-Nutzen-Frage. Wie Ryan erklärte, besteht das Problem darin, dass das Markieren einer Methode als Überschreiben nicht impliziert, dass eine andere Methode _kein_ Überschreiben ist. Sinnvoll wäre es nur, wenn alle Überschreibungen mit einem override -Schlüsselwort markiert werden müssten (was, wenn wir es vorschreiben würden, eine Breaking Change wäre).

@DanielRosenwasser Wie oben beschrieben, ist das Schlüsselwort override in C++ optional (genau wie für Typescript vorgeschlagen), aber jeder verwendet es problemlos und es ist bei großen Codebasen äußerst nützlich. Darüber hinaus ist es in Typescript tatsächlich sehr sinnvoll, dass es optional ist, da die Javascript-Funktion überladen wird.

class Base {
    method(param: number): void { }
}

class DerivedA extends Base {
    // I want to *exactly* implement method with the same signature
    override method(param: number): void {}
}

class DerivedB extends Base {
    // I want to implement method, but support an extended signature
    method(param: number, extraParam: any): void {}
}

Was das ganze Argument "bedeutet nicht, dass eine andere Methode keine Überschreibung ist" betrifft, ist es genau analog zu "privat". Sie können eine ganze Codebasis schreiben, ohne jemals das Schlüsselwort private zu verwenden. Auf einige der Variablen in dieser Codebasis wird immer nur privat zugegriffen und alles wird kompiliert und funktioniert gut. "Private" ist jedoch ein zusätzlicher syntaktischer Zucker, den Sie verwenden können, um dem Compiler zu sagen: "Nein, wirklich, Kompilierungsfehler, wenn jemand versucht, darauf zuzugreifen". Auf die gleiche Weise ist "Überladung" zusätzlicher syntaktischer Zucker, um dem Compiler mitzuteilen: "Ich möchte, dass dies genau mit der Deklaration der Basisklasse übereinstimmt. Wenn es keinen Kompilierfehler gibt".

Weißt du was, ich glaube, ihr seid auf die wörtliche Interpretation von "override" fixiert. Was es wirklich markiert, ist "exactly_match_signature_of_superclass_method", aber das ist nicht ganz so lesbar :)

class DerivedA extends Base {
    exactly_match_signature_of_superclass_method method(param: number): void {}
}

Ich möchte auch das Schlüsselwort override verfügbar haben und den Compiler einen Fehler generieren lassen, wenn die zum Überschreiben markierte Methode in der Basisklasse nicht vorhanden ist oder eine andere Signatur hat. Es würde die Lesbarkeit und das Refactoring verbessern

+1, auch das Werkzeug wird viel besser. Mein Anwendungsfall ist die Verwendung von React. Ich muss die Definition jedes Mal überprüfen, wenn ich die ComponentLifecycle -Methoden verwende:

``` C#
Schnittstelle Komponentenlebenszyklus

{
KomponenteWillMount?(): void;
KomponenteDidMount?(): void;
componentWillReceiveProps?(nextProps: P, nextContext: any): void;
shouldComponentUpdate?(nextProps: P, nextState: S, nextContext: any): boolean;
componentWillUpdate?(nextProps: P, nextState: S, nextContext: any): void;
componentDidUpdate?(prevProps: P, prevState: S, prevContext: any): void;
KomponenteWillUnmount?(): void;
}

With override, or other equivalent solution,you'll get a nice auto-completion. 

One problem however is that I will need to override interface methods...

``` C#
export default class MyControlextends React.Component<{},[}> {
    override /*I want intellisense here*/ componentWillUpdate(nextProps, nextState, nextContext): void {

    }
}

@olmobrutall Es scheint, als wäre Ihr Anwendungsfall besser gelöst, wenn der Sprachdienst Refactorings wie "Schnittstelle implementieren" oder eine bessere Vervollständigung anbietet, und nicht, indem Sie der Sprache ein neues Schlüsselwort hinzufügen.

Lassen Sie sich nicht ablenken :) Sprachdienstfunktionen sind nur ein kleiner Teil der Aufrechterhaltung von Schnittstellenhierarchien in einer großen Codebasis. Der bei weitem größte Gewinn besteht tatsächlich darin, Kompilierzeitfehler zu erhalten, wenn eine Klasse weit unten in Ihrer Hierarchie irgendwo nicht konform ist. Aus diesem Grund hat C++ ein optionales Override-Schlüsselwort hinzugefügt (Non-Breaking Change). Aus diesem Grund sollte Typescript dasselbe tun.

Microsofts Dokumentation für C++-Override fasst die Dinge gut zusammen, https://msdn.microsoft.com/en-us/library/jj678987.aspx

Verwenden Sie override, um ein versehentliches Vererbungsverhalten in Ihrem Code zu verhindern. Das folgende Beispiel zeigt, wo das Memberfunktionsverhalten der abgeleiteten Klasse ohne Verwendung von override möglicherweise nicht beabsichtigt war. Der Compiler gibt keine Fehler für diesen Code aus.
...
Wenn Sie override verwenden, generiert der Compiler Fehler, anstatt automatisch neue Memberfunktionen zu erstellen.

Der bei weitem größte Gewinn besteht tatsächlich darin, Kompilierzeitfehler zu erhalten, wenn eine Klasse weit unten in Ihrer Hierarchie irgendwo nicht konform ist.

Ich muss zustimmen. Eine Falle, die in unseren Teams auftaucht, sind Leute, die denken, sie hätten Methoden außer Kraft gesetzt, obwohl sie sich in Wirklichkeit leicht vertippt oder die falsche Klasse erweitert haben.

Schnittstellenänderungen in einer Basisbibliothek, wenn das Arbeiten über viele Projekte hinweg schwieriger ist, als es in einer Sprache mit einer Agenda wie TypeScript sein sollte.

Wir haben so viele tolle Sachen, aber dann gibt es Kuriositäten wie diese und keine konstanten Eigenschaften auf Klassenebene.

@RyanCavanaugh stimmt, aber der Sprachdienst könnte nach dem Schreiben ausgelöst werden, wie es viele Sprachen tun, ohne das Schlüsselwort ist es viel schwieriger herauszufinden, wann der richtige Moment ist.

Beachten Sie beim Implementieren der Schnittstelle, dass die meisten Methoden in der Schnittstelle optional sind und Sie nur die wenigen benötigen, die Sie benötigen, und nicht das gesamte Paket. Sie könnten einen Dialog mit Kontrollkästchen öffnen, aber trotzdem ...

Und während mein aktueller Schmerzpunkt darin besteht, den Namen der Methode zu finden, wird es in Zukunft großartig sein, mit Kompilierzeitfehlern benachrichtigt zu werden, wenn jemand die Signatur einer Basismethode umbenennt oder ändert.

Könnte die Inkonsistenz nicht einfach durch Hinzufügen einer Warnung behoben werden, wenn Sie keine Überschreibung schreiben? Ich denke, Typoskript macht das Richtige, indem es kleine vernünftige Breaking Changes hinzufügt, anstatt schlechte Entscheidungen für das Ende der Zeit aufzubewahren.

Auch Abstract ist schon da, sie werden ein tolles Paar abgeben :)

Ich hatte auch das Bedürfnis nach einem 'Override'-Bezeichner. Bei mittleren bis großen Projekten wird diese Funktion unerlässlich, und bei allem Respekt hoffe ich, dass das Typescript-Team die Entscheidung, diesen Vorschlag abzulehnen, noch einmal überdenken wird.

Für alle Interessierten haben wir eine benutzerdefinierte tslint-Regel geschrieben, die die gleiche Funktionalität wie das Schlüsselwort override bietet, indem ein Dekorator verwendet wird, ähnlich wie Ryans Vorschlag oben, aber zur Kompilierzeit überprüft. Wir werden es bald freigeben, ich werde zurückschreiben, wenn es verfügbar ist.

Ich hatte auch das starke Bedürfnis, das Schlüsselwort 'override' zu verwenden.
In meinem Fall habe ich einige Methodennamen in einer Basisklasse geändert und vergessen, einige Namen von überschreibenden Methoden umzubenennen. Dies führt natürlich zu einigen Fehlern.

Aber wenn es eine solche Funktion gäbe, können wir diese Klassenmethoden leicht finden.

Wie @RyanCavanaugh erwähnte, sorgt diese Funktion für Verwirrung, wenn dieses Schlüsselwort nur ein optionales Schlüsselwort ist. Wie wäre es also, wenn Sie etwas in tsc markieren, um diese Funktion zu aktivieren?

Bitte überdenken Sie diese Funktion noch einmal....

Wenn das Schlüsselwort override nützlich sein soll, muss es meiner Meinung nach wie in C# erzwungen werden. Wenn Sie in C# eine Methode angeben, die die Signatur einer Basismethode überschreibt, _müssen_ Sie sie entweder override oder new bezeichnen.

C++ ist gegenüber C# an einigen Stellen lästig und unterlegen, weil es zu viele Schlüsselwörter optional macht und daher _Konsistenz_ ausschließt. Wenn Sie beispielsweise eine virtuelle Methode überladen, kann die überladene Methode als virtuell markiert werden oder nicht - in beiden Fällen ist sie virtuell. Ich bevorzuge letzteres, weil es anderen Entwicklern beim Lesen des Codes hilft, aber ich kann den Compiler nicht dazu bringen, das Einfügen zu erzwingen, was bedeutet, dass unsere Codebasis zweifellos fehlende virtuelle Schlüsselwörter haben wird, wo sie wirklich sein sollten. Das Schlüsselwort override ist ebenfalls optional. Beides ist meiner Meinung nach Mist. Was hier übersehen wird, ist, dass Code als Dokumentation dienen und die Wartbarkeit verbessern kann, indem er die Notwendigkeit von Schlüsselwörtern erzwingt, anstatt einen "nimm es oder lass es"-Ansatz. Das Schlüsselwort "throw" in C++ ist ähnlich.

Um das obige Ziel in TypeScript zu erreichen, benötigt der Compiler ein Flag, um dieses stringente Verhalten zu „aktivieren“ oder nicht.

Soweit die Funktionssignaturen der Basis und des Overrides betroffen sind, sollten sie identisch sein. Es wäre jedoch wünschenswert, Kovarianz in der Spezifikation des Rückgabetyps zuzulassen, um eine Prüfung zur Kompilierzeit zu erzwingen.

Ich komme von AS3 zu TS, also gebe ich hier natürlich auch meine Stimme für ein override -Schlüsselwort ab. Für einen Entwickler, der neu in einer bestimmten Codebasis ist, ist das Sehen eines override ein großer Hinweis darauf, was in einer (untergeordneten) Klasse vor sich gehen könnte. Ich denke, dass ein solches Schlüsselwort einen enormen Mehrwert bietet. Ich würde mich dafür entscheiden, es obligatorisch zu machen, aber ich kann sehen, dass dies eine bahnbrechende Änderung wäre und daher optional sein sollte. Ich sehe wirklich kein Problem mit der daraus resultierenden Mehrdeutigkeit, obwohl ich den Unterschied zwischen einem optionalen override und dem standardmäßigen public -Schlüsselwort empfindlich berücksichtige.

Für alle +1er – können Sie darüber sprechen, was an der oben gezeigten Decorator-Lösung nicht ausreicht?

Für mich fühlt es sich wie ein künstliches Konstrukt an, nicht eine Eigenschaft der Sprache selbst. Und ich schätze, das ist beabsichtigt, weil es so ist. Was meiner Meinung nach dem Entwickler (na ja, mir jedenfalls) die Nachricht sendet, dass es vorübergehend ist, keine bewährte Methode.

Offensichtlich hat jede Sprache ihr eigenes Paradigma, und da ich neu bei TypeScript bin, komme ich mit dem Paradigmenwechsel nur langsam voran. Ich muss allerdings sagen, dass sich override aus mehreren Gründen für mich wie eine Best Practice anfühlt. Ich wechsle zu TypeScript, weil ich das stark typisierte Koolaid vollständig geschluckt habe und glaube, dass die Kosten (in Bezug auf Tastenanschläge und Lernkurve) durch die Vorteile sowohl für fehlerfreien Code als auch für das Codeverständnis stark aufgewogen werden. override ist ein sehr wichtiges Teil dieses Puzzles und vermittelt einige sehr wichtige Informationen über die Rolle der überschreibenden Methode.

Für mich geht es weniger um IDE-Komfort, obwohl das unbestreitbar großartig ist, wenn es richtig unterstützt wird, und mehr darum, das zu erfüllen, was meiner Meinung nach die Prinzipien sind, auf denen Sie diese Sprache aufgebaut haben.

@RyanCavanaugh einige Probleme, die ich sehe:

  • Die Decorator-Syntax wird für IDEs schwieriger, Codevervollständigung bereitzustellen (ja, ich denke, es könnte getan werden, aber sie würden Vorkenntnisse benötigen).
  • Die abgeleitete Klasse könnte die Sichtbarkeit einer Methode verändern - was streng genommen nicht erlaubt sein sollte
  • Schwierig für den Dekorateur, die Argumentliste und die Typen sowie den kompatiblen Rückgabetyp zu überprüfen

Der Compiler überprüft jedoch bereits die Argumentliste und den Rückgabetyp. Und ich denke, selbst wenn override als erstklassiges Schlüsselwort existieren würde, würden wir keine neue Regel zur Signaturidentität durchsetzen

Und ich denke, selbst wenn override als erstklassiges Schlüsselwort existieren würde, würden wir keine neue Regel zur Signaturidentität durchsetzen.

@RyanCavanaugh , dann denke ich, dass Sie sich möglicherweise auf einer anderen Seite über die Absicht des Schlüsselworts befinden. Der springende Punkt ist für Fälle, in denen Sie Signaturidentität erzwingen _wollen_. Dies gilt für das Entwurfsmuster, bei dem Sie eine tiefe komplexe Hierarchie haben, die auf einer Basisklasse sitzt, die einen Vertrag von Schnittstellenmethoden definiert, und alle Klassen in der Hierarchie müssen _exakt_ mit diesen Signaturen übereinstimmen. Durch Hinzufügen des Schlüsselworts override zu diesen Methoden in allen abgeleiteten Klassen werden Sie auf alle Fälle aufmerksam gemacht, in denen die Signatur von dem in der Basisklasse festgelegten Vertrag abweicht.

Wie ich immer wieder sage, ist dies kein esoterischer Anwendungsfall. Wenn Sie an einer großen Codebasis arbeiten (mit sagen wir Hunderten oder Tausenden von Klassen), ist dies ein _alltägliches_ Ereignis, dh jemand muss die Signatur der Basisklasse ändern (den Vertrag ändern), und Sie möchten, dass der Compiler Sie darauf hinweist Fälle in der gesamten Hierarchie, die nicht mehr übereinstimmen.

Der Compiler warnt bereits vor illegalen Überschreibungen. Das Problem mit der
Die aktuelle Implementierung ist die fehlende Absicht beim Deklarieren einer Überschreibung
Methode.

Der zuvor erwähnte Dekorateur scheint einfach gegen die Sprache zu verstoßen
versucht zu erreichen. Es sollten keine Laufzeitkosten anfallen
etwas, das zur Kompilierzeit behandelt werden kann, egal wie gering die Kosten sind.
Wir wollen so viele Dinge wie möglich auffangen, die schief gehen, ohne es zu müssen
Führen Sie den Code aus, um es herauszufinden.

Es ist möglich, den Decorator zu verwenden und einen benutzerdefinierten Tslint anzupassen
Regel, wäre aber besser für das Tooling-Ökosystem und die Community
die Sprache, um ein offizielles Schlüsselwort zu haben.

Ich denke, optional zu sein, ist ein Ansatz, aber wie bei einigen C++-Compilern
Es gibt Flags, die Sie setzen können, um seine Verwendung zu erzwingen (z. B. Suggest-Override,
inkonsistent-fehlendes-override). Dies scheint der beste Ansatz zu sein, um dies zu vermeiden
Breaking Changes und scheint konsistent mit anderen neuen Features zu sein, die hinzugefügt werden
kürzlich wie nullable Typen und Dekorateure.

Am Mittwoch, den 23. März 2016 um 21:31 Uhr schrieb Rowan Wyborn [email protected] :

Und ich denke, selbst wenn Override als erstklassiges Stichwort existierte, wir
würde keine neue Regel zur Signaturidentität erzwingen.

@RyanCavanaugh https://github.com/RyanCavanaugh dann denke ich, dass Sie vielleicht
Seien Sie auf einer anderen Seite über die Absicht des Keywords. Der springende Punkt von
es ist für Fälle, in denen Sie Signaturidentität erzwingen _wollen_. Dies
ist für das Designmuster, in dem Sie eine tiefe komplexe Hierarchie sitzen haben
auf einer Basisklasse, die einen Vertrag von Schnittstellenmethoden definiert, und alle
Klassen in der Hierarchie müssen _exakt_ mit diesen Signaturen übereinstimmen. Beim Hinzufügen
Sie sind das Schlüsselwort override für diese Methoden in allen abgeleiteten Klassen
auf alle Fälle aufmerksam gemacht, in denen die Unterschrift vom Vertrag abweicht
in der Basisklasse.

Wie ich immer wieder sage, ist dies kein esoterischer Anwendungsfall. Bei der Arbeit an a
Bei großen Codebasen (mit beispielsweise Hunderten oder Tausenden von Klassen) ist dies ein _every
Tag_ Auftreten, dh jemand muss die Signatur der Basis ändern
Klasse (ändern Sie den Vertrag), und Sie möchten, dass der Compiler Sie auf alle hinweist
Fälle in der gesamten Hierarchie, die nicht mehr übereinstimmen.


Sie erhalten dies, weil Sie diesen Thread abonniert haben.
Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -200551774

@kungfusheep fwiw Der Compiler fängt nur eine bestimmte Klasse illegaler Überschreibungen ab, d. h. es gibt einen direkten Konflikt zwischen deklarierten Parametern. Es fängt _nicht_ das Hinzufügen oder Entfernen von Parametern ab, noch fängt es Änderungen des Rückgabetyps ab. Diese zusätzlichen Überprüfungen sind das, was jedes Override-Schlüsselwort einschalten würde.

Der Compiler warnt Sie richtig, wenn Sie einen Parameter zu einer überschreibenden Funktion _hinzufügen_.

Das Entfernen eines Parameters ist jedoch _absolut gültig_:

class BaseEventHandler {
  handleEvent(e: EventArgs, timestamp: number) { }
}

class DerivedEventHandler extends BaseEventHandler {
  handleEvent(e: EventArgs) {
    // I don't need timestamp, it's OK
  }
}

Das Ändern des Rückgabetyps ist auch _völlig gültig_:

class Base {
  specialClone(): Base { ... }
}
class Derived extends Base {
  specialClone(): Derived { ... }
}

@RyanCavanaugh ja, sie sind aus streng sprachlicher Sicht gültig, aber deshalb habe ich angefangen, oben den Begriff "Vertrag" zu verwenden. Wenn eine Basisklasse eine bestimmte Signatur anlegt, gebe ich bei der Verwendung von Override an, dass meine abgeleitete Klasse dieser Signatur strikt folgen soll. Wenn die Basisklasse zusätzliche Parameter hinzufügt oder den Rückgabetyp ändert, möchte ich jeden Punkt in meiner Codebasis wissen, der nicht mehr übereinstimmt, da sich der Vertrag, für den sie ursprünglich geschrieben wurden, jetzt in irgendeiner Weise geändert hat.

Die Idee, eine große Klassenhierarchie mit jeweils eigenen, leicht unterschiedlichen Permutationen der Basismethoden zu haben (auch wenn sie aus sprachlicher Sicht gültig sind), ist ein Albtraum und versetzt mich zurück in die schlechten alten Tage von Javascript, bevor Typoskript auftauchte :)

Wenn die Optionalität das Hauptproblem mit dem Schlüsselwort ist, warum machen Sie es dann nicht obligatorisch, wenn die Methode der Basisklasse als abstract definiert ist? Wenn Sie das Muster streng durchsetzen möchten, können Sie auf diese Weise einfach eine abstrakte Basisklasse hinzufügen. Um das Brechen von Code zu vermeiden, könnte ein Compiler-Schalter die Prüfung deaktivieren.

Wir scheinen jetzt über zwei verschiedene Arten von Erwartungen zu sprechen. Es gibt die Situation mit fehlender Überschreibung/illegaler Überschreibung und dann die explizite Signatur. Können wir uns alle darauf einigen, dass Ersteres das absolute Minimum ist, das wir von diesem Schlüsselwort erwarten würden?

Ich sage dies nur, weil es derzeit andere Möglichkeiten gibt, explizite Methodensignaturen zu erzwingen, z. B. Schnittstellen, aber es gibt derzeit keine Möglichkeit, die Absicht zum Überschreiben explizit anzugeben.

Ok, es gibt keine Möglichkeit, explizite Signaturen von überschriebenen Methoden zu erzwingen, aber da der Compiler erzwingt, dass alle Signaturänderungen zumindest "sicher" sind, scheint es, als müsste eine separate Konversation über die Lösung dieses Problems geführt werden.

Ja zugestimmt. Wenn ich mich entscheiden müsste, wäre die Situation der fehlenden Überschreibung/illegalen Überschreibung das wichtigere Problem, das es zu lösen gilt.

Ich bin ein bisschen spät zur Party ... Ich denke, der ganze Sinn von Typescript besteht darin, Regeln zur Kompilierzeit durchzusetzen, nicht zur Laufzeit, sonst würden wir alle einfaches Javascript verwenden. Außerdem ist es ein bisschen seltsam, Hacks/Kludges zu verwenden, um Dinge zu tun, die in so vielen Sprachen Standard sind.
Sollte es in Typoskript ein Schlüsselwort override geben? Das glaube ich auf jeden Fall. Soll es zwingend sein? Aus Kompatibilitätsgründen würde ich sagen, dass sein Verhalten mit einem Compiler-Argument angegeben werden könnte. Soll es die exakte Signatur erzwingen? Ich denke, dies sollte eine separate Diskussion sein, aber ich hatte bisher keine Probleme mit dem aktuellen Verhalten.

Der ursprüngliche Grund für das Schließen scheint zu sein, dass wir nicht die Breaking Change einführen können, dass alle überschriebenen Methoden den Bezeichner override benötigen, was es verwirrend machen würde, da die Methoden, die nicht mit override gekennzeichnet sind, ebenfalls enthalten sein könnten Tatsache außer Kraft gesetzt werden.

Warum nicht erzwingen, entweder mit einer Compiler-Option, und/oder wenn es _mindestens eine_ mit override markierte Methode in der Klasse gibt, dann müssen alle Methoden, die außer Kraft gesetzt werden, als solche markiert werden, sonst ist es ein Fehler?

Lohnt es sich, dies wieder zu öffnen, während es in der Luft ist?

Am Freitag, den 8. April 2016 um 14:38 Uhr schrieb Peter Palotas [email protected] :

Der ursprüngliche Grund für die Schließung scheint zu sein, dass wir nicht einführen können
die bahnbrechende Änderung, dass alle überschriebenen Methoden die Überschreibung benötigen
Bezeichner, was es verwirrend machen würde, da die Methoden nicht mit gekennzeichnet sind
„Override“ könnte tatsächlich auch Overrides sein.

Warum nicht erzwingen, entweder mit einer Compiler-Option und/oder wenn es eine gibt
Mindestens 'eine' Methode, die in der Klasse als Override markiert ist, dann alle Methoden, die es sind
Überschreibungen müssen als solche gekennzeichnet werden, sonst handelt es sich um einen Fehler?


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -207434898

Ich sage bitte wieder öffnen! Ich würde mich über eine Compiler-Option freuen.

ja - bitte wieder öffnen

Öffne das wieder!

Erfordert ES7 nicht, dass mehrere Methoden überschrieben/überladen werden, die dieselben haben
Name?
Am 8. April 2016 um 10:56 Uhr schrieb „Aram Taieb“ [email protected] :

Ja, bitte wieder öffnen!


Sie erhalten dies, weil Sie diesen Thread abonniert haben.
Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -207466464

+1 - für mich ist dies die größte Lücke in der Typsicherheit, die ich in der täglichen TypeScript-Entwicklung habe.

Jedes Mal, wenn ich eine React-Lebenszyklusmethode wie „componentDidMount“ implementiere, suche ich nach der entsprechenden React-Dokumentationsseite und kopiere/füge den Methodennamen ein, um sicherzustellen, dass ich keinen Tippfehler habe. Ich mache das, weil es 20 Sekunden dauert, während die Fehler durch einen Tippfehler indirekt und subtil sind und es viel länger dauern kann, sie aufzuspüren.

Klasse mit 5 Methoden, von denen 3 als Override markiert sind, würde nicht bedeuten, dass die anderen 2 keine Overrides sind. Um seine Existenz zu rechtfertigen, müsste der Modifikator die Welt wirklich sauberer aufteilen.

Wenn dies das Hauptproblem ist, rufen Sie das Schlüsselwort check_override auf, um deutlich zu machen, dass Sie sich dafür entscheiden, die Überprüfung zu überschreiben, und implizit werden die anderen Methoden _nicht überprüft_ und nicht _nicht überschrieben_.

Was ist mit implements. ? So etwas sein:

class MyComponent extends React.Component<MyComponentProps, void>{
    implements.componentWillMount(){
        //do my stuff
    }
}

Diese Syntax hat einige Vorteile:

  • Es verwendet ein bereits vorhandenes Schlüsselwort.
  • Nach dem Schreiben implements. hat die IDE eine hervorragende Gelegenheit, ein Popup für die automatische Vervollständigung anzuzeigen.
  • Das Schlüsselwort verdeutlicht, dass es verwendet werden kann, um die Überprüfung abstrakter Methoden von Basisklassen, aber auch von implementierten Schnittstellen zu erzwingen.
  • Die Syntax ist mehrdeutig genug, um auch für Felder wie diese verwendet werden zu können:
class MyComponent<MyComponentProps, MyComponentState> {
    implements.state = {/*auto-completion for MyComponentState here*/};

    implements.componentWillMount(){
        //do my stuff
    }
}

Hinweis: Alternativ könnten wir base. verwenden, es ist Sortierer und intuitiver, aber vielleicht verwirrender (Definieren oder Aufrufen?) und die Bedeutung ist nicht so kompatibel mit der Schnittstellenimplementierung. Beispiel:

class MyComponent<MyComponentProps, MyComponentState> {
    base.state = {/*auto-completion for MyComponentState here*/};

    base.componentWillMount(){ //DEFINING
        //do my stuff
        base.componentWillMount(); //CALLING
        //do other stuff
    }
}

Ich glaube nicht, dass implements alle Anwendungsfälle ausreichend abdeckt
bereits erwähnt und es ist ein wenig mehrdeutig. Mehr gibt es nicht
Informationen an eine IDE, die override auch nicht konnte, also scheint es so
sinnvoll, bei der Terminologie zu bleiben, an die sich viele andere Sprachen gewöhnt haben
dasselbe erreichen.
Am Mittwoch, den 13. April 2016 um 19:06 Uhr schrieb Olmo [email protected] :

Was ist mit der Verwendung von Geräten? So etwas sein:

Klasse MyComponent erweitert React.Component{
implements.componentWillMount(){
// mache mein Zeug
}
}

Diese Syntax hat einige Vorteile:

  • Es verwendet ein bereits vorhandenes Schlüsselwort.
  • Nach Schreibgeräten. die IDE hat eine hervorragende Gelegenheit, sich zu zeigen
    eine Autovervollständigungsmethode.
  • Das Schlüsselwort verdeutlicht, dass dies verwendet werden kann, um die Überprüfung von Abstracts zu erzwingen
    Methoden von Basisklassen, aber auch implementierte Schnittstellen.
  • die Syntax ist mehrdeutig genug, um auch für Felder verwendbar zu sein, wie z
    Das:

Klasse MyComponent{
implements.state = {/_automatische Vervollständigung für MyComponentState hier_/};

implements.componentWillMount(){
    //do my stuff
}

}


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -209571753

Das Problem beim Überschreiben ist, dass Sie, sobald Sie dasselbe Schlüsselwort verwenden, dieselben Erwartungen haben:

  • es sollte obligatorisch sein, zumindest wenn die Methode abstract ist.
  • Sie vermissen dann ein virtual -Schlüsselwort, um Methoden zu kommentieren, die überschrieben werden sollen, aber ein Standardverhalten haben.

Ich denke, das TS-Team möchte der Sprache nicht so viel OO-Gepäck hinzufügen, und ich denke, das ist eine gute Idee.

Durch die Verwendung von implements. haben Sie eine leichtgewichtige Syntax, um die Hauptvorteile zu nutzen: automatische Vervollständigung und zur Kompilierzeit überprüfter Name, ohne neue Schlüsselwörter zu erfinden oder die Konzeptanzahl zu erhöhen.

Hat auch den Vorteil, für Klassen und Schnittstellen und für Methoden (im Prototyp) oder direkte Felder zu arbeiten.

Möglichkeiten, override obligatorisch zu machen, wurden bereits im Thread besprochen, und die Lösungen unterscheiden sich nicht von anderen vom Compiler implementierten Funktionen.

Das Schlüsselwort virtual ergibt im Kontext der Sprache keinen wirklichen Sinn und ist auch nicht so benannt, dass es für Leute intuitiv ist, die zuvor noch nie mit Sprachen wie C++ gearbeitet haben. Vielleicht wäre eine bessere Lösung, um diese Art von Schutz bei Bedarf bereitzustellen, das Schlüsselwort final .

Ich stimme zu, dass Sprachfeatures nicht hastig hinzugefügt werden sollten, die 'Gepäck' erzeugen könnten, aber override stopft eine legitime Lücke im Vererbungssystem, die viele andere Sprachen für notwendig erachtet haben. Seine Funktionalität wird am besten mit einem neuen Schlüsselwort erreicht, insbesondere dann, wenn alternative Ansätze grundlegende Syntaxänderungen vorschlagen sollen.

Was ist mit dem Festlegen geerbter Felder im Vergleich zum Neudefinieren neuer Felder? Reaktion state ist ein gutes Beispiel.

Oder die Implementierung optionaler Schnittstellenmethoden?

override könnte möglicherweise für Felder verwendet werden, wenn sie neu deklariert werden
im Körper der Unterklasse. Optionale Schnittstellenmethoden sind keine Überschreibungen, sind es aber
außerhalb des Rahmens dessen, worüber wir hier sprechen.

Am Donnerstag, den 14. April 2016 um 11:58 Uhr schrieb Olmo [email protected] :

Was ist mit dem Festlegen geerbter Felder im Vergleich zum Neudefinieren neuer Felder? Zustand reagieren
ist ein gutes Beispiel.

Oder die Implementierung optionaler Schnittstellenmethoden?


Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -209879217

Optionale Schnittstellenmethoden sind keine Überschreibungen und liegen daher außerhalb des Bereichs dessen, worüber wir hier sprechen.

Optionale Schnittstellenmethoden sind meines Wissens ein ziemlich einzigartiges Konzept für TypeScript, und ich denke, eine TypeScript-Implementierung von "Überschreibungen" sollte für sie gelten.

Im Fall von React-Lebenszyklusmethoden wie componentDidMount sind dies optionale Schnittstellenmethoden, die keine Implementierung in der Oberklasse haben.

Im Fall von React-Lebenszyklusmethoden wie componentDidMount sind dies optionale Schnittstellenmethoden, die keine Implementierung in der Oberklasse haben.

Genau, damit sie nichts überschreiben. Ich denke, wir sind verwirrt darüber, was das Schlüsselwort override hier bieten soll, denn wenn Sie nur nach einer Möglichkeit suchen, bessere Codehinweise/Intellisense zu erhalten, gibt es andere Möglichkeiten, die ohne Hinzufügen erreicht werden können neue Schlüsselwörter für die Sprache.

Leute, bei allem Respekt, können wir bitte konzentriert bleiben. Alternative Keywords zu diskutieren halte ich für kontraproduktiv, zumal die Ausgabe mit einer konkreten Anfrage gestartet wurde.

Können wir uns entweder alle darauf einigen, dass wir override brauchen und das Typescript-Team freundlich bitten, es hinzuzufügen, oder die Anfrage fallen lassen und das Problem schließen lassen?

Ich denke, es ist immer nützlich, die Anfrage im Zusammenhang mit dem Problem zu stellen, es gibt immer verschiedene Möglichkeiten, das Problem zu lösen. Der Knackpunkt beim Überschreiben lag darin, ob es obligatorisch ist oder nicht (einige machen darauf aufmerksam, dass es in C++ nicht obligatorisch ist). Das Problem, auf das viele Leute verwiesen haben, besteht darin, den richtigen Namen für überschreibbare Funktionen zu finden (die möglicherweise in der Superklasse implementiert sind oder nicht) - die Lebenszyklusfunktionen von React sind das Hauptproblem.

Wenn das Überschreiben nicht funktioniert, sollten wir dieses Problem vielleicht schließen und einen allgemeineren Punkt zu diesem Problem eröffnen. Das allgemeine Problem hier ist, dass der Schnittstellenvertrag mit einer Oberklasse nicht typisiert und nicht überprüft ist, und das bringt Entwickler ins Stolpern, und es wäre großartig, wenn TS oder die Tools helfen könnten.

@armandn Ich glaube nicht, dass unser Mangel an Schließung oder die Freundlichkeit unseres Vorschlags die Anfrage abgelehnt hält, sondern die Unterschiede zwischen C # und TS-Semantik:

Überschreibung der C#-Basismethode:

  • Erfordert das Schlüsselwort override
  • Erstellt einen neuen Datensatz auf VTable
  • Prüft auf obligatorisches Abstract/Virtual in der Basismethode
  • Prüft auf identische Signatur
  • IDE: Löst die automatische Vervollständigung nach dem Schreiben der Überschreibung aus

Implementierung der C#-Schnittstelle:

  • Kein Schlüsselwort notwendig, aber explizite Schnittstellenimplementierung über den Schnittstellennamen möglich.
  • Erstellen Sie einen neuen Schnittstellendatensatz in der VTable.
  • Prüft auf identische Signatur
  • IDE: QuickFix für Implement-Schnittstelle / Implement-Schnittstelle explizit.

Das Verhalten in C# ist also ziemlich unterschiedlich, je nachdem, ob Sie nach Klassen oder Schnittstellen fragen, aber in jedem Fall erhalten Sie die drei Hauptvorteile:

  1. Prüfung auf identische Signatur
  2. Prüfen, ob die Methode überschrieben werden kann/soll (Rechtschreibfehler im Methodennamen)
  3. IDE unterstützt das Schreiben der Funktion

Mit etwas anderer Semantik haben wir bereits 1) in TS, leider fehlen 2) und 3). Der Grund, warum override meiner Meinung nach abgelehnt wurde, ist, dass eine ähnliche Syntax ein ähnliches Verhalten voraussetzt, und dies nicht wünschenswert ist, weil:

Eine Klasse mit 5 Methoden, von denen 3 als Override markiert sind, würde nicht bedeuten, dass die anderen 2 keine Overrides sind.

Es kommt vor, dass wir 1) 2) und 3) bereits haben, wenn wir _Objektliterale_ schreiben, aber nicht, wenn wir Klassenmitglieder schreiben, die andere Klassen erweitern oder Schnittstellen implementieren.

Vor diesem Hintergrund denke ich, dass wir uns alle auf diese Semantik einigen können:

  • Das _Schlüsselwort_ sollte mehrdeutig genug sein, um das Problem der „geteilten Welt“ zu vermeiden. (dh: check oder super. oder MyInterface. )
  • Es wird nicht auf abstract/virtual geprüft, sondern auf die Existenz von Membern in der Basisklasse / den implementierten Schnittstellen. (Vorteil 2)
  • Prüft auf ähnlich ausreichende Signaturkompatibilität, wie es TS bisher tut. (Vorteil 1)
  • IDE: Löst eine automatische Vervollständigung nach dem Schlüsselwort aus. (Vorteil 3)

Außerdem denke ich, dass diese beiden für die Vollständigkeit der Lösung notwendig sind *:

  • Es sollte für Klassen und Schnittstellen funktionieren, da TS hauptsächlich schnittstellenbasiert ist und Klassen eine Abkürzung für prototypische Vererbung sind. Notwendig unter Berücksichtigung optionaler Schnittstellenmethoden.
  • Es sollte für Methoden und Felder funktionieren, da Methoden nur Funktionen sind, die sich in einem Feld befinden.

Diese beiden Punkte sind im sehr realen Anwendungsfall der Implementierung von React-Komponenten nützlich:

``` C#
Klasse MyComponent{
implements.state = {/ automatische Vervollständigung für MyComponentState hier /};

implements.componentWillMount(){
    //do my stuff
}

}
```

Die annotationsbasierte Lösung von @RyanCavanaugh reicht nicht aus, weil:

  • Mit Schnittstellen funktioniert es nicht.
  • Mit Feldern funktioniert es nicht.
  • Unter der Annahme einer Roslyn-ähnlichen Infrastruktur zur Unterstützung der Tools gibt es keine Möglichkeit, einen QuickFix hinzuzufügen, nachdem eine Autovervollständigungsliste nach dem Schreiben @override geschrieben wurde.

Angesichts der Semantik geht es nur darum, die richtige Syntax zu wählen. Hier einige Alternativen:

  • override componentWillMount() : Intuitiv, aber irreführend.
  • check componentWillMount() : Explizit, aber schlüsselwortintensiv.
  • super componentWillMount() :
  • implements componentWillMount() :
  • super.componentWillMount() :
  • implements.componentWillMount() :
  • this.componentWillMount() :
  • ReactComponent.componentWillMount() :

Meinungen?

@olmobrutall gute Zusammenfassung. Paar Punkte:

Eine Schlüsselwortoption (aus C# übernommen) wäre neu – was auf einen neuen Slot hinweist:

class MyComp extends React.Component<IProps,IState> {
...
    new componentWillMount() { ... }
    componentWillMount() { ...} // would compile, maybe unless strict mode is enabled
    new componentwillmount() { ... } <-- error

Mein anderer Punkt ist das Problem mit der obligatorischen Natur der Verwendung des oben Genannten. Als Vertrag zwischen der übergeordneten Superklasse und der abgeleiteten Klasse ist es sinnvoll, anzugeben, an welchen Schnittstellenpunkten die obige Syntax gültig wäre. Dies sind wirklich interne Erweiterungspunkte für die Superklasse, also so etwas wie:

class Component<P,S> {
    extendable componentWillMount() {...}
}

Dasselbe würde auch für die Schnittstelle gelten.

Danke :)

Wie wäre es, wenn Sie einfach this schreiben?

class MyComponent<MyComponentProps, MyComponentState> {
    this.state = {/*auto-completion for MyComponentState here*/};

    this.componentWillMount(){
        //do my stuff
    }
}

Etwa extendable , es gibt bereits abstract in TS 1.6, und das Hinzufügen von Virtual wird vielleicht wieder das Problem der geteilten Welt schaffen?

Richtig, ich dachte an Abstract , aber es könnte eine Implementierung in der Superklasse geben, also macht es keinen Sinn. Ebenso mit virtual , da dies implizieren würde, dass nicht virtuelle Mitglieder nicht virtuell sind - was irreführend ist.

this. funktioniert, ich denke, Sie könnten sogar (als Langform) haben:

   this.componentWillMount = () => { }

Das einzige Problem dabei ist, dass es nur auf zweckgebundene Erweiterungspunkte beschränkt werden sollte, nicht auf alle Mitglieder der Basisklasse.

Was ist los...

TypeScript ist und war nie die Javascript-Version von C#. Der Grund liegt also nicht darin, dass sich die vorgeschlagene Funktionalität von der Semantik von C# unterscheidet. Die Gründe für die Schließung, wie sie von Ryan angegeben und dann von Daniel R. erläutert wurden, waren

Wie Ryan erklärte, besteht das Problem darin, dass das Markieren einer Methode als Außerkraftsetzung nicht impliziert, dass eine andere Methode keine Außerkraftsetzung ist. Sinnvoll wäre es nur, wenn alle Überschreibungen mit einem Überschreibungsschlüsselwort gekennzeichnet werden müssten (was, wenn wir es vorschreiben würden, eine Breaking Change wäre).

Sie bestehen jedoch weiterhin auf Ihren Problemen mit der automatischen Vervollständigung. Die automatische Vervollständigung benötigt kein neues Schlüsselwort in der Sprache, um Ihnen verbesserte Vorschläge zu liefern.

Der Grund für diesen Thread besteht darin, Compilerfehler zu erhalten, wenn eine Funktion illegal überschrieben wird oder wenn eine Methode als Überschreibung deklariert wurde, aber keine Funktion in einer Basisklasse tatsächlich überschrieben hat. Es ist ein einfaches und gut definiertes Konzept für viele Sprachen, das auch die meisten, wenn nicht mehr Sprachfunktionen unterstützt, die Typoskript zu bieten hat. Es muss nicht alle Probleme der Welt lösen, es muss nur das Schlüsselwort override für Überschreibungen sein.

Seitdem haben mehr Menschen Interesse an dem ursprünglichen Vorschlag gezeigt, also bleiben wir bei dem Thema, für das das Problem angesprochen wurde, und werfen neue Probleme für neue Ideen auf.

TypeScript ist und war nie die Javascript-Version von C#.

Ich vergleiche es mit C# als Referenzsprache, da ich annehme, dass dies das Verhalten ist, von dem ihr ausgeht.

Die automatische Vervollständigung benötigt kein neues Schlüsselwort in der Sprache, um Ihnen verbesserte Vorschläge zu liefern.

Wie schlagen Sie vor, es dann auszulösen? Wenn Sie beim Schreiben eines zufälligen Namens in einem Klassenkontext eine Autocomplete-Combobox anzeigen, ist dies sehr ärgerlich, wenn wir nur ein neues Feld oder eine neue Methode deklarieren möchten.

Der Grund für diesen Thread besteht darin, Compilerfehler zu erhalten, wenn eine Funktion illegal überschrieben wird oder wenn eine Methode als Überschreibung deklariert wurde, aber keine Funktion in einer Basisklasse tatsächlich überschrieben hat.

Ich habe diesen Anwendungsfall unbedingt unter Vorteil 2 aufgenommen.

Es muss nicht alle Probleme der Welt lösen, es muss nur das Schlüsselwort override für Überschreibungen sein.

Ihr Vorschlag ist also, _Schritt für Schritt_ zu beheben, anstatt einen Schritt zurückzugehen und ähnliche/verwandte Probleme zu betrachten?. Das ist vielleicht eine gute Idee für Ihr Scrum Board, aber nicht für das Entwerfen von Sprachen.

Der Grund, warum sie beim Hinzufügen des Schlüsselworts konservativ sind, ist, dass sie keine Funktionen aus einer Sprache entfernen können .

Einige Designfehler in C# wegen fehlender Fertigstellung:

  • Kovarianz/Kontravarianz des unsicheren Arrays.
  • var funktioniert nur für Variablentypen, nicht für automatische generische Parameter oder Rückgabetypen. Vielleicht ist auto ein besseres Schlüsselwort wie in C++?

Versuchen Sie einfach, React für eine Sekunde zu verwenden, und Sie sehen die andere Seite des Bildes.

Das Überschreiben einer Methode, die bereits in einer Basisklasse implementiert wurde, und das Implementieren einer Schnittstellenmethode sind _ zwei völlig verschiedene Dinge _. Also ja, was vorgeschlagen wird, ist, eines dieser Szenarien mit einem speziellen Schlüsselwort zu beheben, anstatt zu versuchen, ein Schlüsselwort der Schweizer Armee zu finden.

Was nützt ein Schlüsselwort, das für Entwickler, die einen Code zum ersten Mal lesen, eine von drei verschiedenen Bedeutungen haben kann? Es ist mehrdeutig und verwirrend, besonders wenn Sie über die Verwendung eines Schlüsselworts wie this sprechen, das bereits andere (völlig unabhängige!) Dinge in der Sprache tut - es könnte nicht allgemeiner sein, es ist fast nutzlos.

Wenn Ihr Hauptanliegen die automatische Vervollständigung ist, haben Redakteure _jetzt_ genügend Informationen, um Methoden aus Basisklassen und implementierten Schnittstellen vorschlagen zu können, „während Sie tippen“.

Das Überschreiben einer Methode, die bereits in einer Basisklasse implementiert wurde, und das Implementieren einer Schnittstellenmethode sind zwei völlig verschiedene Dinge.

Im Allgemeinen ja, aber wir sprechen hier nicht über die Implementierung irgendeiner Schnittstellenmethode. Wir sprechen über eine optionale Interface-Methode, _bei der die übergeordnete Klasse das Interface implementiert_. In diesem Fall können Sie sagen, dass 1) die Schnittstelle zulässt, dass die Methode als undefined implementiert wird, 2) die übergeordnete Klasse eine undefinierte Implementierung hat und 3) die untergeordnete Klasse die undefinierte Implementierung mit einer Methodenimplementierung überschreibt.

@olmobrutall Ich denke, Ihr Kommentar zum Entwerfen von Sprachen und dass es sich nicht um ein Scrum-Board handelt, ist ein wenig eigennützig. Ich habe innerhalb von weniger als einem Jahr ungefähr vier Updates für TS gesehen.

Wenn das Sprachdesign so gut durchdacht wäre, wie Sie es implizieren, dann gäbe es bereits ein Sprachspezifikationsdokument, das uns genau sagt, wie Überschreibungen funktionieren sollten, und wir würden diese Unterhaltung vielleicht nicht einmal führen.

Ich mache diesen Kommentar nicht mit irgendeiner Verunglimpfung der TS-Entwickler/Designer, weil TS bereits exzellent ist und ich mich davor fürchten würde, stattdessen Standard-JS verwenden zu müssen.

Ja, TS ist nicht C# und es ist nicht C++. Aber eine Reihe von Sprachen haben das Schlüsselwort override gewählt, um die hier diskutierten Ziele zu erreichen, so dass es kontraproduktiv erscheint, eine völlig fremde Syntax vorzuschlagen.

Das Hauptproblem scheint darin zu bestehen, keine bahnbrechende Änderung einführen zu wollen. Die einfache Antwort ist ein Compiler-Flag, Ende der Geschichte. Für einige Leute wie mich ist ein optionales Override-Schlüsselwort nutzlos. Für andere möchten sie ihren Code schrittweise verschönern. Compiler-Flag löst das Dilemma.

Signaturunterschiede sind ein anderes Gespräch. Das Schlüsselwort new erscheint unnötig, da JS nicht mehrere Methoden mit demselben Namen unterstützen kann (es sei denn, TS erstellt signaturabgeleitete verstümmelte Namen a'la C++, was höchst unwahrscheinlich ist).

Ich habe innerhalb von weniger als einem Jahr ungefähr vier Updates für TS gesehen.

Ich meine nicht, dass Sie nicht schnell sein und schnell iterieren können. Ich bin so glücklich wie jeder andere, dass sich ES6 und TS schnell weiterentwickeln. Was ich meine ist, dass man versuchen muss, die Zukunft vorherzusagen, um die Sprache nicht in eine Sackgasse zu führen.

Ich könnte der Verwendung des Schlüsselworts override zustimmen. Mit den richtigen Argumenten werden sogar Felder und Schnittstellen aus dem Geltungsbereich herausgehalten, aber ich kann dem Argument '_lassen Sie uns konzentriert bleiben und nur dieses spezielle Problem lösen, wie es andere Sprachen tun, ohne zu viel nachzudenken_' nicht zustimmen.

Aber eine Reihe von Sprachen haben das Schlüsselwort override gewählt, um die hier diskutierten Ziele zu erreichen, so dass es kontraproduktiv erscheint, eine völlig fremde Syntax vorzuschlagen.

Keine dieser Sprachen hat eine prototypische Vererbung oder optionale Methode (Methoden, die weder abstrakt noch virtuell sind, sie könnten zur Laufzeit einfach _nicht existieren_), und dies sind verwandte Probleme, die diskutiert (und möglicherweise verworfen) werden müssen, bevor eine Verpflichtung eingegangen wird.

Anders ausgedrückt: Nehmen wir an, wir tun, was Sie vorzuschlagen scheinen, und wir implementieren Override, ohne groß darüber nachzudenken. Dann füge ich oder jeder andere, der TSX verwendet, ein Problem hinzu, warum override nicht mit React-Komponenten funktioniert. Was ist Ihr Plan?

Im Allgemeinen ja, aber wir sprechen hier nicht über die Implementierung irgendeiner Schnittstellenmethode. Wir sprechen von einer optionalen Schnittstellenmethode, bei der die übergeordnete Klasse die Schnittstelle implementiert.

Es spielt keine Rolle, wo die Schnittstelle eingerichtet wurde, Tatsache ist, dass sie nicht dasselbe sind und daher kein gemeinsames Schlüsselwort haben sollten, weil die _Absicht_ des Programms nicht klar ist.

Sie könnten beispielsweise eine Methode überschreiben, die für die Schnittstellenkonformität in einer Basisklasse implementiert wurde; Wenn wir alle unsere Eier für diese beiden unterschiedlichen Dinge in ein Schlüsselwort stecken würden, wie würde dann jemand wissen, ob dies die anfängliche Deklaration dieser Funktion oder eine Überschreibung einer zuvor in einer Basisklasse definierten Funktion ist? Sie würden es nicht tun, und es wäre nicht möglich, dies ohne weitere Untersuchung der Basisklasse zu wissen - die möglicherweise sogar in einer .d.ts -Datei eines Drittanbieters enthalten ist, was es zu einem absoluten Albtraum macht, sie zu finden, je nachdem, wie tief sie ist in der Vererbungskette wurde die Funktion ursprünglich implementiert.

Anders ausgedrückt: Nehmen wir an, wir tun, was Sie vorzuschlagen scheinen, und wir implementieren Override, ohne groß darüber nachzudenken. Dann füge ich oder jeder andere, der TSX verwendet, ein Problem hinzu, warum das Überschreiben mit React-Komponenten nicht funktioniert. Was ist Ihr Plan?

Warum muss dies React reparieren? Wenn React ein anderes Problem hat als das, was es zu lösen versucht, kann ich beim besten Willen nicht verstehen, warum override es beheben muss? Haben Sie versucht, ein anderes Thema zu öffnen, um vorzuschlagen, dass etwas bezüglich der Schnittstellenimplementierung getan werden sollte?

Ich würde nicht zustimmen, dass nicht genug darüber nachgedacht wurde. Wir schlagen eine erprobte und erprobte Technik vor, die in jeder anderen mir einfallenden Sprache, die sie implementiert hat, erfolgreich war.

Tatsache ist, dass sie nicht dasselbe sind und daher kein Schlüsselwort teilen sollten, da die Absicht des Programms nicht klar ist.

Sind nicht? Schauen Sie sich diese beiden alternativen Definitionen von BaseClass an

class BaseClass {
     abstract myMethod(); 
}
interface ISomeInterface {
     myMethod?(); 
}

class BaseClass extends ISomeInterface {
}

Und dann machst du in deinem Code:

``` C#
Klasse Betonklasse {
überschreibe meineMethode() {
// Sachen machen
}
}

You think it should work in just one case and not in the other? The effect is going to be 100% identical in Javascript (creating a new method in ConcreteClass prototype), from the external interface and from the tooling perspective. 

Even more, maybe you want to capture `this` inside of the method, implementing it with a lambda (useful for React event handling). In this case you'll write something like this:

``` C#
class ConcreteClass {
    override myMethod = () => { 
         // Do stuff
    }
}

Das Verhalten ist wieder identisch, wenn die Methode abstrakt ist oder von der Schnittstelle kommt: Fügen Sie ein Feld in der Klasse mit einem Lambda hinzu, das es implementiert. Aber override einem Feld sieht es etwas seltsam aus, da Sie nur einen Wert zuweisen.

Sehen wir es uns nicht mit super. an (im Moment meine Lieblingssyntax, aber ich bin offen für Alternativen).

``` C#
Klasse Betonklasse {
super.myMethod() { //Methode im Prototyp
// Sachen machen
}

super.myMethod = () => {  //method in lambda
     // Do stuff
}

}
```

Jetzt ist das Konzept konzeptionell einfacher: Meine Superklasse sagt, dass es eine Methode oder ein Feld gibt und die ConcreteClass es im Prototyp definieren / zuweisen / lesen / aufrufen kann.

Warum muss dies React reparieren?

Ist nicht nur reagieren, schau eckig:

  • Reagieren : 58 Schnittstellen und 3 Klassen.
  • Angular : 108 Schnittstellen und 11 Klassen.

Natürlich sind die meisten Interfaces nicht dazu gedacht, implementiert zu werden, und auch nicht alle Klassen, die überschrieben werden sollen, aber eines ist klar: In Typescript sind Interfaces wichtiger als Klassen.

Haben Sie versucht, ein anderes Thema zu öffnen, um vorzuschlagen, dass etwas bezüglich der Schnittstellenimplementierung getan werden sollte?

Wie soll ich es nennen? override für Schnittstelle und Felder?

Wir schlagen eine erprobte und erprobte Technik vor, die in jeder anderen mir einfallenden Sprache erfolgreich war.

Die Sprachen, die Sie im Sinn haben, sind ganz andere. Sie haben eine Vererbung basierend auf einer statischen VTable.

In Typescript ist eine Klasse nur eine Schnittstelle + automatische Prototypvererbung von Methoden. Und Methoden sind nur Felder mit einer Funktion darin.

Um die override -Funktion an TS anzupassen, müssen diese zwei grundlegenden Unterschiede berücksichtigt werden.

Bitte @kungfusheep macht sich die Mühe, über eine Lösung für mein Problem nachzudenken. Wenn Sie verschiedene Keywords hinzufügen und in einer zweiten Phase implementieren möchten, ist das in Ordnung, aber nehmen Sie sich eine Sekunde Zeit, um sich vorzustellen, wie es sein sollte.

Mir fällt keine andere Möglichkeit ein, das zu sagen, was ich bereits gesagt habe. Sie sind nicht gleich. Sie sind ähnlich, aber nicht gleich. Bitte lesen Sie diesen Kommentar von einem der TS-Entwickler RE: das readonly-Schlüsselwort - https://github.com/Microsoft/TypeScript/pull/6532#issuecomment -179563753 - das bekräftigt, was ich sage.

Ich stimme der allgemeinen Idee zu, aber sehen wir uns die Praxis an:

class MyComponent extends React.Component<{ prop : number }, { value: string; }> {

    //assign a field defined in the base class without re-defining it (you want type-checking)
    assign state = { value : number}; 

    //optional method defined in an interface implemented by the base class    
    implement componentDidMount(){ 
    }

    //abstract method defined in the base class 
    override render(){  
    }
}

Das sieht aus wie VB oder Cobol, nicht wahr?

Es sieht zumindest so aus, als würde es Sinn machen.

Betrachten Sie dieses Beispiel, wenn nur das Schlüsselwort override (oder nur ein Schlüsselwort) vorhanden wäre.

interface IDo {
    do?() : void;
}
class Component implements IDo {
    protected commitState() : void {
        /// do something
    }
    override public do() : void {
        /// base implements 'do' in this case
    }
}

Lassen Sie uns nun unsere eigene Komponente mit dem, was wir gerade geschrieben haben, implementieren.

class MyComponent extends Component {
    override protected commitState(){
        /// do our own thing here
        super.commitState();
    }
    override do() : void {
        /// this is ambiguous. Am I implementing this from an interface or overriding a base method? I have no way of knowing. 
    }

}

Eine Möglichkeit, das herauszufinden, wäre der Typ von super :

  override do() : void {
        super.do(); // this compiles, if it was an interface then super wouldn't support `do`
    }

exakt! was bedeutet, dass das Design falsch ist. Es sollte keine Untersuchung mit dem Skimming von Code verbunden sein, es sollte nur Sinn machen, wenn Sie es lesen.

das ist zweideutig. Implementiere ich dies über eine Schnittstelle oder überschreibe ich eine Basismethode? Ich kann es nicht wissen.

Was ist der Unterschied in der Praxis? Das Objekt hat nur einen Namensraum und Sie können nur ein Lambda im Feld do platzieren. Es gibt sowieso keine Möglichkeit, eine Schnittstelle explizit zu implementieren. Die Umsetzung wird sein:

MyComponent.prototype.do = function (){
    //your stuff
}

unabhängig davon was du schreibst.

Es spielt keine Rolle, was die Ausgabe ist. Sie können absichtlich oder unabsichtlich einige Funktionen in einer Basisklasse überschreiben, aber ein mehrdeutiges Schlüsselwort enthält keine Absicht.

Welcher Fehler oder welches unerwartete Verhalten wird durch zwei Schlüsselwörter behoben?

Komm schon, Kumpel. Du bist offensichtlich ein kluger Kerl. Ich habe gerade gesagt, dass „irgendeine Funktionalität in einer Basisklasse unbeabsichtigt überschrieben wird“; Können wir daraus nicht auf ein unerwartetes Verhalten schließen, das möglicherweise gerade aufgetreten ist?

Um es klarzustellen, ich schlage nicht vor, dieses Problem in einen Vorschlag für zwei Schlüsselwörter umzuwandeln. Dieses Problem betrifft das Schlüsselwort override - alles andere erfordert einen neuen Vorschlag und eine eigene Diskussion zur Semantik.

Um es klarzustellen, ich schlage nicht vor, dieses Problem in einen Vorschlag für zwei Schlüsselwörter umzuwandeln. Dieses Problem betrifft das Schlüsselwort override - alles andere erfordert einen neuen Vorschlag und eine eigene Diskussion zur Semantik.

Dabei spielt es keine Rolle, in wie vielen Themen diskutiert werden soll oder von wem die Idee kommt. Sie schlagen vor, zwei sehr verwandte Gedanken zu trennen und ziehen nicht einmal eine konsistente Syntax in Betracht .

Die Argumente dafür, ob wir 1, 2 oder 3 Schlüsselwörter brauchen, gehören zu diesem Thread und sind noch nicht fertig (...aber wiederholen sich). Dann können wir vielleicht die Syntax in einem anderen Thread diskutieren (weil die Semantik sowieso identisch sein wird :P).

In meinem Beispiel:

class MyComponent extends React.Component<{ prop : number }, { value: string; }> {

    //assign a field defined in the base class without re-defining it (you want type-checking)
    assign state = { value : number}; 

    //optional method defined in an interface implemented by the base class    
    implement componentDidMount(){ 
    }

    //abstract method defined in the base class 
    override render(){  
    }
}

Machen assign , implement und override nicht genau dasselbe: Überprüfen Sie, ob der Name existiert (in der Basisklasse, den implementierten Schnittstellen, den von der Basis implementierten Schnittstellen Klasse usw.).

Wenn es einen Namenskonflikt zwischen Basisklassen und einigen implementierten Schnittstellen gibt, erhalten Sie einen Kompilierungsfehler, egal ob mit 1, 2 oder überhaupt keinem Schlüsselwort.

Denken Sie auch an das Objektliteral:

var mc = new MyComponent(); 
mc.state = null;
mc.componentDidMount =null;
mc.render = null;

Mit genau derselben Syntax kann ich Felder oder Methoden unabhängig voneinander neu zuweisen, wenn sie aus der Basisklasse, direkten Schnittstellenimplementierungen oder in der Basisklasse implementierten Schnittstellen stammen.

Nicht zuweisen, implementieren und überschreiben tun genau dasselbe: Überprüfen Sie, ob der Name existiert (in der Basisklasse, den implementierten Schnittstellen, den von der Basisklasse implementierten Schnittstellen usw.).

Sie haben dort gerade 3 verschiedene Szenarien beschrieben, also sind sie offensichtlich nicht gleich. Ich habe das Gefühl, ich könnte den ganzen Tag beschreiben, warum sie anders sind als Sie, und Sie würden immer noch dasitzen und argumentieren, dass sie es nicht sind, also werde ich mich vorerst aus dieser speziellen Diskussionslinie verabschieden. Es spricht sowieso nichts dafür, dass die TS-Jungs im Moment noch darüber nachdenken.

Mit der Schließung von #6118 gibt es meiner Meinung nach Anlass zu diskutieren, ob die Probleme dort und die Probleme hier gleichzeitig angegangen werden können.

https://github.com/Microsoft/TypeScript/pull/6118 war mir nicht bekannt. Die Idee sieht nach einer möglichen Alternative zum Hinzufügen override aus.

Wenn ich das Problem richtig verstanden habe, besteht das Problem darin, dass Sie mehr als eine Basisklasse / Schnittstelle mit kompatiblen, aber nicht identischen Elementdeklarationen haben können, und diese müssen vereinheitlicht werden, wenn sie in der untergeordneten Klasse ohne Typ initialisiert werden.

Ohne eine genaue Vorstellung von den Konsequenzen zu haben, würde ich mich freuen, wenn:

  • Der Member erzeugt einen Kompilierzeitfehler, der eine explizite Typdeklaration für die untergeordnete Klasse erfordert
  • Das Mitglied nimmt den Schnittpunkt aller möglichen Typen.

Wichtiger wäre meiner Meinung nach eine Möglichkeit, die automatische Vervollständigung auszulösen, wenn Klassenmitglieder geschrieben werden (Strg+Leertaste). Wenn sich der Cursor direkt innerhalb einer Klasse befindet, könnten Sie neue Mitglieder definieren oder geerbte neu definieren, sodass die automatische Vervollständigung nicht zu aggressiv sein kann, aber bei manueller Auslösung sollte das Verhalten in Ordnung sein.

Zu den Kommentaren von @RyanCavanaugh :

Wir verstehen die Anwendungsfälle hier definitiv. Das Problem ist, dass das Hinzufügen zu diesem Zeitpunkt in der Sprache mehr Verwirrung stiftet als beseitigt. Eine Klasse mit 5 Methoden, von denen 3 als Override markiert sind, würde nicht bedeuten, dass die anderen 2 keine Overrides sind. Um seine Existenz zu rechtfertigen, müsste der Modifikator die Welt wirklich sauberer aufteilen.

Die Eingabe einer Variablen als any bedeutet nicht, dass eine andere Variable auch any ist oder nicht. Aber es gibt eine Compiler-Flat --no-implicit-any , um zu erzwingen, dass wir sie explizit deklarieren. Wir könnten auch einen --no-implicit-override haben, den ich für meinen Teil anmachen würde, wenn er verfügbar wäre.

Ein override -Schlüsselwort zu haben, das verwendet wird, gibt Entwicklern einen enormen Einblick, wenn sie Code lesen, mit dem sie nicht vertraut sind, und die Möglichkeit, es zu erzwingen, würde eine zusätzliche Kontrolle über die Kompilierzeit geben.

Für alle +1er – können Sie darüber sprechen, was an der oben gezeigten Decorator-Lösung nicht ausreicht?

Gibt es eine Möglichkeit, in der ein Dekorateur eine bessere Lösung ist als ein Überschreibungsschlüsselwort? Es gibt viele Gründe, warum es schlimmer ist: 1) Es fügt Laufzeit-Overhead hinzu, wie klein es auch sein mag; 2) Es ist keine Überprüfung der Kompilierzeit; 3) Ich muss diesen Code in jede meiner Bibliotheken einfügen; 4) Es gibt keine Möglichkeit, Funktionen abzufangen, denen das Schlüsselwort override fehlt.

Lassen Sie uns ein Beispiel geben. Ich habe eine Bibliothek mit drei Klassen ChildA , ChildB , Base . Ich habe eine Methode doSomething() zu ChildA und ChildB hinzugefügt. Nach einigem Refactoring habe ich zusätzliche Logik hinzugefügt, optimiert und doSomething() in die Klasse Base verschoben. Inzwischen habe ich ein anderes Projekt, das von meiner Bibliothek abhängt. Dort habe ich ChildC mit doSomething() . Es gibt keine Möglichkeit, wenn ich meine Abhängigkeiten aktualisiere, um herauszufinden, dass ChildC jetzt doSomething() implizit überschreibt, aber auf eine nicht optimierte Weise, bei der auch einige Überprüfungen fehlen. Aus diesem Grund wird ein @overrides Dekorateur niemals ausreichen.

Was wir brauchen, ist ein Schlüsselwort override und ein Compiler-Flag --no-implicit-override .

Ein override -Schlüsselwort würde mir sehr helfen, da ich in meinem Projekt eine einfache Hierarchie von Basisklassen verwende, um alle meine Komponenten zu erstellen. Mein Problem liegt in der Tatsache, dass diese Komponenten möglicherweise eine Methode deklarieren müssen, die an anderer Stelle verwendet werden soll, und diese Methode möglicherweise in einer übergeordneten Klasse definiert ist oder nicht und möglicherweise bereits Dinge tut oder nicht, die ich benötige.

Nehmen wir zum Beispiel an, dass eine Funktion validate eine Klasse mit einer Methode getValue() als Parameter akzeptiert. Um diese Klasse zu erstellen, kann ich eine andere Klasse erben, die diese getValue() -Methode bereits definieren könnte, aber ich kann das nicht wirklich wissen, es sei denn, ich schaue mir ihren Quellcode an. Ich würde diese Methode instinktiv in meiner eigenen Klasse implementieren, und solange die Signatur korrekt ist, wird mir niemand etwas sagen.

Aber vielleicht hätte ich das nicht tun sollen. Die folgenden Möglichkeiten setzen alle voraus, dass ich eine implizite Überschreibung vorgenommen habe:

  1. Die Basisklasse hat diese Methode bereits genau wie ich definiert, also habe ich eine Funktion umsonst geschrieben. Nicht so ein Problem.
  2. Ich habe die Funktion fälschlicherweise umgeschrieben, meistens weil ich vergessen habe, einige nicht offensichtliche Dinge zu tun, die bereits von der Basisklasse behandelt wurden. Was ich hier wirklich tun musste, war super in meinem Override zu rufen, aber niemand schlug mir vor, dies zu tun.

Ein obligatorisches Override-Schlüsselwort hätte mir gesagt: "Hey, Sie überschreiben, also sollten Sie vielleicht überprüfen, wie die ursprüngliche Methode aussieht, bevor Sie Ihre Sachen machen". Das würde meine Erfahrung mit Erbschaften erheblich verbessern.

Natürlich sollte es, wie vorgeschlagen, unter einer --no-implicit-override -Flagge platziert werden, da es eine bahnbrechende Änderung wäre und die meisten Leute sich nicht so sehr darum kümmern.

Ich mag den Vergleich, den @eggers mit any und --no-implicit-any gemacht hat, weil es die gleiche Art von Anmerkung ist und auf genau die gleiche Weise funktionieren würde.

@olmobrutall Ich habe mir einige Ihrer Vorträge über das Überschreiben abstrakter Methoden und Schnittstellenmethoden angesehen.

Meiner Meinung nach impliziert override die Existenz eines super. Weder abstrakte Methoden noch Methoden, die in einer Schnittstelle definiert sind, können über einen super. -Aufruf aufgerufen werden und sollten daher nicht überschreibbar sein ( es gibt nichts zu überschreiben). Wenn wir stattdessen etwas expliziteres für diese Fälle tun, sollte es ein implements -Schlüsselwort sein. Aber das wäre eine separate Feature-Diskussion.

Hier sind die Probleme, wie ich sie sehe, in der Reihenfolge am wichtigsten bis am wenigsten. Habe ich etwas vergessen?

Fehler beim Überschreiben

Es ist leicht zu _denken_, dass Sie eine Basisklassenmethode überschreiben, wenn dies nicht der Fall ist:

class Base {
  hasFilename(f: string) { return true; }
}
class Derived extends Base {
  // oops
  hasFileName(f: string) { return false; }
}

Das ist wahrscheinlich das größte Problem.

Fehler bei der Umsetzung

Dies hängt sehr eng zusammen, insbesondere wenn die implementierte Schnittstelle optionale Eigenschaften hat:

interface NeatMethods {
  hasFilename?(f: string): boolean;
}
class Mine implements NeatMethods {
  // oops
  hasFileName(f: string) { return false; }
}

Dies ist ein weniger wichtiges Problem als _Fehler beim Überschreiben_, aber es ist immer noch schlimm.

Versehentliches Überschreiben

Es ist möglich, eine Basisklassenmethode zu überschreiben, ohne es zu merken

class Base {
  hasFilename(f: string) { return true; }
}
class Derived extends Base {
  // I didn't know there was a base method with this name, so oops?
  hasFilename(f: string) { return true; }
}

Dies sollte relativ selten vorkommen, nur weil der Platz für mögliche Methodennamen sehr groß ist, und die Wahrscheinlichkeit, dass Sie auch eine kompatible Signatur schreiben würden _und_ dies gar nicht beabsichtigt haben, gering ist.

Versehentlich any s

Es ist leicht zu glauben, dass Override-Methoden die Parametertypen ihrer Basis erhalten:

class Base {
  hasFilename(f: string) { return true; }
}
class Derived extends Base {
  // oops
  hasFilename(f) { return f.lentgh > 0; }
}

Wir haben versucht, diese Parameter automatisch einzugeben, sind aber auf Probleme gestoßen. Wenn hasFilename ausdrücklich mit override gekennzeichnet wäre, könnten wir diese Probleme möglicherweise einfacher lösen.

Lesbarkeit

Es ist nicht sofort klar, wann eine Methode eine Basisklassenmethode überschreibt und wann nicht. Dies scheint ein kleineres Problem zu sein, da es heute vernünftige Problemumgehungen gibt (Kommentare, Dekorateure).

Editor-Erfahrung

Sie müssen Basismethodennamen manuell eingeben, wenn Sie abgeleitete Klassenüberschreibungsmethoden schreiben, was ärgerlich ist. Wir könnten dies leicht beheben, indem wir diese Namen immer in der Vervollständigungsliste angeben (ich glaube, wir haben tatsächlich bereits einen Fehler darin), also brauchen wir nicht wirklich eine Sprachfunktion, um dieses Problem zu beheben.

Keine Probleme

Listen Sie diese als Dinge auf, die entweder in eine separate Ausgabe gehören oder einfach nicht passieren werden:

  • Genauer Signaturabgleich – das sollte nicht mit override vermischt werden, und es hat in vielen guten Szenarien wirklich eine unerwünschte Semantik
  • Unzureichend exotische Syntax (zB implements.foo = ... ). Fahrradschuppen ist hier nicht nötig

@RyanCavanaugh Ich stimme all dem oben Genannten auch in dieser Reihenfolge zu. Nur als Kommentar, ich denke nicht, dass das Schlüsselwort override das Problem "Fehler bei der Implementierung" lösen sollte. Wie ich oben sagte, denke ich, dass dieses Problem durch ein anderes Schlüsselwort (z. B. implements ) als Teil eines anderen Tickets gelöst werden sollte. ( override sollte eine super. Methode implizieren)

@RyanCavanaugh Ich stimme allen Punkten zu, aber ich sehe keinen Hinweis auf das ebenfalls sehr verwandte Problem initialisierter Felder, die in einem übergeordneten Typ deklariert werden, ohne den Typ zu überschreiben (und dann die Typprüfung zu verlieren). Ich denke, das Feature sollte nicht auf Methodendeklarationen in einer Sprache beschränkt sein, in der Methoden nur Funktionen in Objektfeldern sind.

@eggers auch wenn am Ende zwei Schlüsselwörter benötigt werden, wird die Semantik sehr ähnlich sein und ich denke, die Features sollten gemeinsam besprochen werden.

override sollte ein super implizieren. Methode

In C# kann (und muss) override auch für abstrakte Methoden (ohne .super) verwendet werden. In Java ist @override ein Attribut. Natürlich sind sie unterschiedliche Sprachen, aber bei ähnlichen Schlüsselwörtern erwarten Sie ähnliche Verhaltensweisen.

Ich stimme den Punkten von @RyanCavanaugh zu, obwohl ich sagen würde, dass das Problem des "versehentlichen Überschreibens" etwas häufiger auftritt, als er glaubt (insbesondere wenn es um die Erweiterung einer Klasse geht, die bereits etwas anderes erweitert, wie eine React-Komponente, die dies bereits tun würde Definieren Sie eine componentWillMount -Methode in der ersten Erweiterung).

Ich glaube jedoch, wie @eggers sagte, dass das Problem "Fehler bei der Implementierung" etwas ganz anderes ist, und es würde sich seltsam anfühlen, ein Schlüsselwort override für eine Methode zu verwenden, die in einer übergeordneten Klasse nicht vorhanden ist. Ich weiß, dass dies verschiedene Sprachen mit unterschiedlichen Problemen sind, aber in C# wird override nicht für Schnittstellen verwendet. Ich würde vorschlagen, wenn wir ein --no-implicit-override -Flag bekommen könnten, dass Schnittstellenmethoden der Schnittstellenname vorangestellt werden müsste (was C# sehr ähnlich sehen und sich daher natürlicher anfühlen würde).

Etwas wie

interface IBase {
    method1?(): void
}

class Base {
    method2() { return true; }
}

class Test extends Base implements IBase {
    IBase.method1() { }
    override method2() { return true; }
}

Ich denke, @RyanCavanaugh listet die grundlegenden Probleme auf. Ich würde das mit "Was sind die Meinungsverschiedenheiten" ergänzen:

  • Wird das Deklarieren einer Methode zur Übereinstimmung mit einer Schnittstelle als override betrachtet? Zum Beispiel:
    interface IDrawable
    {
        draw?( centerPoint: Point ): void;
    }

    class Square implements IDrawable
    {
        draw( centerPoint: Point ): void; // is this an override?
    }
  • deklariert eine Eigenschaft so, dass sie mit einer Schnittstelle übereinstimmt, die als override betrachtet wird?
    interface IPoint2
    {
        x?: number;
        y?: number;
    }

    class Circle implements IPoint2
    {
        x: number; // is this an override?
        y: number; // is this an override?
        radius: number;
    }
  • Gibt es eine Notwendigkeit für eine Art implements -Schlüsselwort, um die oben genannten Fälle anstelle eines override -Schlüsselworts zu handhaben, und die Form, die es annehmen würde.

@ kevmeister68 danke für die ausdrückliche Angabe der Meinungsverschiedenheiten.

Eine Sache: Sie müssen die Interface-Member optional machen, um das Problem wirklich zu zeigen, so wird sich der Typescript-Compiler beschweren, wenn ein Feld oder eine Methode nicht implementiert ist, und "Failure to implement" lösen.

@olmobrutall Danke dafür. Ich habe noch nie eine Methode als optional deklariert - kann das sein (ich komme aus einem C#-Hintergrund und es gibt kein solches Konzept)? Ich habe die oben aufgeführten Beispiele aktualisiert, um die Mitgliedsvariablen in optional zu ändern - ist das besser?

@kevmeister68 auch die Methode draw in IDrawable :)

@RyanCavanaugh

Unzureichend exotische Syntax (z. B. implements.foo = ...). Fahrradschuppen ist hier nicht nötig

Danke, dass du mir einen neuen Ausdruck beigebracht hast. Ich sehe nicht viele Probleme in der Semantik des Features:

  • Wir alle wollen Kompilierungsfehler, wenn die Methode nicht existiert
  • Wir alle wollen Kompilierungsfehler, deren Typ nicht übereinstimmt.
  • Wir möchten auch, dass die Parameter implizit in den übergeordneten Parameter typisiert werden, wie dies bei Lambda-Ausdrücken der Fall ist.
  • Wir brauchen etwas IDE-Hilfe, um die Methode richtig zu schreiben.

Die meisten Probleme hängen von der Syntax und den Verhaltensweisen ab, die sie implizieren:

  • Optionale Member in Interface im Vergleich zu Membern in Basisklassen
  • Methoden vs. Felder (weiß nicht, wo Lambdas sein werden)

Vergleichen wir den Baum mit vielversprechenderen Syntaxen:

überschreiben / implementiert

class Person{
    dateOfBirth: Date;

    abstract talk();
    walk(){ //...}
}

interface ICanFly{
    fly?();
    altitude?: number;
}


class SuperMan extends Person implements ICanFly {
     dateOfBirth = new Date(); //what goes here?

     override talk(){/*...*/}
     walk = () => {/* force 'this' to be captured*/}  //what goes here

     implements fly() {/*...*/}
     altitude = 1000; //what goes here?
}

Vorteile:

  • Es ist natürlich für Programmierer mit OO-Hintergrund.
  • Fühlt sich richtig an, wenn man es mit extends / implements vergleicht.

Nachteile:

  • Bietet keine Lösung für das Feldproblem, keine override oder implements fühlt sich richtig an. Vielleicht super. , aber was machen wir dann mit Schnittstellen?
  • Wenn die Methoden mit einem Lambda (nützlich, um dies zu erfassen) von function() überschrieben werden, treten die gleichen Probleme auf.

überschreiben / InterfaceName.

class Person{
    dateOfBirth: Date;

    abstract talk();
    walk(){ //...}
}

interface ICanFly{
    fly?();
    altitude?: number;
}


class SuperMan extends Person implements ICanFly {
     dateOfBirth = new Date(); //what goes here?

     override talk(){/*...*/}
     walk = () => {/* force 'this' to be captured*/}  //what goes here

     ICanFly.fly() {/*...*/}
     ICanFly.altitude = 1000; //what goes here?
}

Vorteile:

  • Ist auch für Programmierer mit OO-Hintergrund selbstverständlich.
  • Löst das Feldproblem für Schnittstellen, aber nicht für Klassen, aber wir könnten super. dafür verwenden.

Nachteile:

  • Schnittstellennamen können lang sein, insbesondere bei Generika, und das Anzeigen der Auto-Wettbewerbsliste ist ebenfalls problematisch, da etwas Alt + Leerzeichen erforderlich ist, um sie deutlich zu machen.
  • Es sieht so aus, als könnten Sie zwei Mitglieder mit demselben Namen unabhängig von verschiedenen Schnittstellen implementieren. Dies funktioniert in C#, aber nicht in Javascript.

this.

class Person{
    dateOfBirth: Date;

    abstract talk();
    walk(){ //...}
}

interface ICanFly{
    fly?();
    altitude?: number;
}


class SuperMan extends Person implements ICanFly {
     this.dateOfBirth = new Date();

     this.talk(){/*...*/}
     this.walk = () => {/* force 'this' to be captured*/} 

     this.fly() {/*...*/}
     this.altitude = 1000;
}

Vorteile:

  • Löst Klassen, Schnittstellen, Methoden und Felder.
  • Verwendet (missbraucht?) nur ein bereits vorhandenes Schlüsselwort.
  • Die Verwendung this. zum Auslösen der automatischen Vervollständigung fühlt sich natürlich an.
  • Macht deutlich, dass das Objekt nur ein Namespace ist und Sie nur eine Sache für jeden Namen haben können.

Nachteile:

  • Sieht aus wie ein Ausdruck, nicht wie eine Deklaration, was es für ungeschulte Augen schwierig macht, sie zu analysieren.

Während das Versäumnis, eine nicht-optionale Schnittstellenmethode zu implementieren, einen Compilerfehler verursacht, wird es keine Warnung für alle Klassen geben, die diese Methode implementiert haben, wenn sich die Schnittstelle irgendwann in der Zukunft ändert (sagen wir, eine Methode wird entfernt). Dies ist möglicherweise kein so großes Problem wie das optionale, aber ich denke, es ist fair zu sagen, dass der Umfang davon über sie hinausgeht.

Ich glaube jedoch immer noch, dass override eine deutlich andere Sache ist und möglicherweise zwei Schlüsselwörter anstelle von einem die Absicht des Programms besser vermitteln würden.

Stellen Sie sich zum Beispiel ein Szenario vor, in dem ein Entwickler _glaubt_, dass er eine Schnittstellenmethode implementiert, obwohl er tatsächlich die Methode überschreibt, die bereits in einer Basisklasse deklariert wurde. Mit zwei Schlüsselwörtern kann der Compiler diese Art von Fehlern verhindern und einen Fehler im Ton von method already implemented in a base class .

interface IDelegate {
    execute?() : void;
}

class Base implements IDelegate {
    implement public execute() : void { /// fine, this is correctly implementing execute
    }
}

class Derived extends Base {
    implement public execute() : void { 
/// ERROR: `method "execute():void" already implemented in a base class`
    }
}

Ich weiß nicht, ob dies in JS relevant ist, aber Klassenfelder sollen in OO privat sein, also denke ich nicht, dass wir Felder explizit überschreiben müssen, da die Tatsache, dass sie privat sind, uns bereits daran hindert, sie insgesamt zu überschreiben .

Instanzmethoden sind jedoch Felder, und soweit ich weiß, verwenden viele Leute sie anstelle von Prototypmethoden. Über das,

class Person{
    walk(){ //...}
}
class SuperMan extends Person  {
     walk = () => {/* force 'this' to be captured*/}
}

ist ein Compilerfehler, da walk eine Prototypmethode in Person und eine Instanzmethode in SuperMan ist.

Wie auch immer, ich bin mir nicht sicher, ob override hier passen würde, weil wir keine Felder überschreiben. Auch hier würde es wie C# aussehen, aber ich würde hier lieber das Schlüsselwort new anstelle von override verwenden. Weil es nicht dasselbe ist wie eine Methodenüberschreibung (ich kann super.myMethod in einer Überschreibung aufrufen, nicht hier).

Meine bevorzugte Lösung wäre dann so etwas wie (vorausgesetzt, wir befinden uns im strikten Überschreibungsmodus):

class Person{
    dateOfBirth: Date;
    talk() { }
    walk = () => { }
}

interface ICanFly {
    fly?();
    altitude?: number;
}

class SuperMan extends Person implements ICanFly {
     new dateOfBirth = new Date();
     override talk() { }
     new walk = () => { }

     implements fly() {/*...*/}
     implements altitude = 1000;
}

Meine Hauptsorge gilt jedoch den Schnittstellen. Wir sollten implements nicht für nicht-optionale Interface-Member schreiben müssen, da wir sonst schon vom Compiler abgefangen werden. Wenn wir dies tun, wird es dann eine Verwirrung zwischen dem geben, was von einer Schnittstelle stammt und was nicht, da nicht allen Schnittstellenmitgliedern das Präfix implements vorangestellt wird. Und dieses Wort ist ein Schluck. Ich bin nicht bereit, so etwas zu schreiben:

class C extends React.Component {
    implements componentWillMount() { }
    implements componentDidMount() { }
    implements componentWillReceiveProps(props) { }
    /// ... and the list goes on
}

Ich habe früher vorgeschlagen, den Schnittstellennamen voranzustellen, aber @olmobrutall hat mir gezeigt, dass dies eine schlechtere Idee ist.

Wie auch immer, ich bin ziemlich überzeugt, dass wir 3 verschiedene neue Keywords brauchen, um das richtig anzugehen.

Ich denke auch nicht, dass es notwendig ist, die Überschreibungen implizit einzugeben, da der Compiler uns bereits daran hindert, etwas zu schreiben, das nicht kompatibel ist, insbesondere weil es anscheinend schwierig ist, es richtig zu machen.

Ist new , override und implement nicht zu viel Keyword-Overhead für im Wesentlichen dieselbe JS-Semantik? Auch wenn es in C# verschiedene Dinge sind, gibt es praktisch keinen Unterschied in JS/TS.

Es ist auch ziemlich zweideutig:

  • Warum sind Felder new für Klassen, aber implements für Schnittstellen?
  • Wenn Sie eine Methode aus einer Basisklasse ändern, die mit einer Schnittstelle implementiert wurde, was sollten Sie verwenden? Ich sehe vier Möglichkeiten: override , implements , eine der beiden oder beide gleichzeitig?

Denken Sie auch darüber nach:

class Animal {
}

class Human extends Animal {
}

class Habitat {
    owner: Animal;
}

class House extends Habitat {
    owner = new Human();
}

var house = new House();
house.owner = new Dog(); //Should this be allowed??  

Die Frage ist:

Definiert owner = new Human(); das Feld mit einem neuen (aber kompatiblen) Typ neu oder weist nur einen Wert zu.

Ich denke, im Allgemeinen haben Sie das Feld neu definiert, außer wenn Sie das _magische Schlüsselwort_ verwenden. Ich bevorzuge im Moment this. .

class House extends Habitat {
    this.owner = new Human(); //just setting a value, the type is still Animal
}

@olmobrutall Das könnte in der Tat ein bisschen viel Keyword-Overhead sein, aber alle 3 sind in der Tat _verschiedene_ Dinge.

  • override würde auf (im Prototyp definierte) Methoden angewendet, was bedeutet, dass die ursprüngliche Methode _nicht_ gelöscht wird, auf die hinter super noch zugegriffen werden kann.
  • new würde für Felder gelten und bedeuten, dass das ursprüngliche Feld durch das neue gelöscht wurde.
  • implements würde auf alles von einer Schnittstelle zutreffen und bedeuten, dass es nichts löscht oder ersetzt, was bereits in einer übergeordneten Klasse existiert, weil eine Schnittstelle "nicht existiert".

Wenn Sie eine Methode aus einer Basisklasse ändern, die mit einer Schnittstelle implementiert wurde, was sollten Sie verwenden? Ich sehe vier Möglichkeiten: override , implements , eines der beiden oder beide gleichzeitig?

Es erscheint mir ziemlich logisch, dass es override wäre, da Sie das hier tatsächlich tun. implements würde bedeuten, dass Sie etwas von der Schnittstelle implementieren, das sonst nicht existiert. In gewisser Weise hätten override und new Vorrang vor implements .

Und an Ihrem Feldüberschreibungsbeispiel sollte sich nichts an der heutigen Funktionsweise ändern. Ich bin mir nicht sicher, was hier passiert, aber was auch immer es ist, es sollte sich nicht ändern (oder sollte es vielleicht, aber das ist nicht das, was wir hier besprechen).

Ich bin der Meinung, dass override sowohl für Basismethoden als auch für Eigenschaften geeignet wäre, einschließlich abstrakter (siehe unten).

new ist vom Konzept her eine interessante Idee, aber dieses spezielle Schlüsselwort kann mit der Klasseninstanzierung verwechselt werden, sodass es den falschen Eindruck erwecken würde, dass es nur für Klassen funktionieren würde, obwohl Eigenschaften Primitive, Interface, Union haben können oder sogar Funktionstypen. Vielleicht könnte ein anderes Schlüsselwort wie reassign dort besser funktionieren, aber es hat das Problem, dass es eine falsche Vorstellung vermitteln kann, falls der Wert nur neu deklariert, aber tatsächlich nichts in der abgeleiteten Klasse zugewiesen wird ( new könnte dieses Problem ebenfalls haben). Ich denke, redefine ist auch interessant, könnte aber zu der falschen Erwartung führen, dass die 'redefinierte' Eigenschaft auch einen anderen Typ als die Basis haben könnte, also bin ich mir nicht sicher.. (_edit: Eigentlich habe ich nachgesehen und es könnte einen anderen Typ haben, solange der neue ein Untertyp des Basistyps ist, also ist dies vielleicht nicht so schlimm_).

implement (ich bevorzuge diese spezielle Verbform wegen der Konsistenz mit override ) scheint für Schnittstellen zu funktionieren. Ich glaube, es könnte technisch auch für abstrakte Basismethoden funktionieren, aber die Verwendung override könnte sich trotz der etwas anderen Semantik konsistenter anfühlen. Ein weiterer Grund ist, dass es etwas unpraktisch wäre, eine Methode von abstrakt auf nicht abstrakt zu ändern, da man zu allen abgeleiteten Klassen gehen und override in implement ändern müsste.

Es könnte bessere Ideen geben, aber das ist alles, was ich an diesem Punkt habe.

Ich habe das Schlüsselwort new vorgeschlagen, weil es das ist, das C# für genau diese Funktion verwendet, aber ich stimme zu, dass dies nicht das beste ist. implement ist in der Tat eine bessere Wahl als implements . Ich denke nicht, dass wir es für abstrakte Methoden verwenden sollten, da sie Teil einer Basisklasse und nicht einer Schnittstelle sind. override + new -> Basisklasse, implement -> Schnittstelle. Das scheint klarer.

@JabX

Informationen zum Überschreiben des Prototyps mit dem Instanzfeld

ich habe nachgeschaut und du hast recht:

class Person{
    walk(){ //...}
}
class SuperMan extends Person  {
     walk = () => {/* force 'this' to be captured*/}
}

Schlägt mit Compiler-Fehler fehl: Class 'Person' defines instance member function 'walk', but extended class 'SuperMan' defines it as instance member property.

Ich sehe in diesem Fall keinen wirklichen Grund zum Scheitern, da der übergeordnete Vertrag erfüllt ist und Sie dies trotzdem schreiben können:

var p = new SuperMan();
p.walk = () => { };

Oder auch

class SuperMan extends Person {
    constructor() {
        super();
        this.walk = () => { };
    }
}

Ungefähr override

override würde auf Methoden (die im Prototyp definiert sind) angewendet, was bedeutet, dass die ursprüngliche Methode nicht gelöscht wird, auf die hinter super noch zugegriffen werden kann.

Das ist eigentlich ein Argument. Zwischen override und implements gibt es den wenigsten _einen_ beobachtbaren Unterschied ... aber nicht ganz.

Sowohl override als auch implements sind in prototype implementiert, die Verwendung super ist unabhängig, da Sie super.walk() und nicht nur super() aufrufen

class SuperMan extends Person implements ICanFly {
     new dateOfBirth = new Date();
     override talk() { } //goes to prototype
     new walk = () => { }

     implements fly() {/*...*/}  //also goes to the prototype
     implements altitude = 1000;
}

Und die Verwendung super.walk() funktioniert auch, wenn Sie der Instanz zuweisen

class SuperMan extends Person {
    constructor() {
        super();
        this.walk = () => { super.walk(); };
    }

    //or with the this. syntax
    this.walk = () => { super.walk(); };
}

Es gibt bereits eine klare Möglichkeit, prototype -Felder von Instanzfeldern zu unterscheiden, und zwar das = -Token.

class SuperMan extends Person implements ICanFly {
     this.dateOfBirth = new Date(); //instance
     this.talk() { } //prototype
     this.walk = () => { } //instance

     this.fly() {/*...*/}  //prototype
     this.altitude = 1000; //instance
}

Mit der this. Syntax wird das Problem eleganter gelöst:

  • this. bedeutet, meinen Typ zu überprüfen (Basisklassen und implementierte Schnittstellen).
  • = bedeutet, der Instanz statt dem Prototypen zuzuweisen.

Ungefähr New

new würde für Felder gelten und bedeuten, dass das ursprüngliche Feld durch das neue gelöscht wurde.

Berücksichtigen Sie, dass Sie das Feld oder die Methode nicht mit einem anderen Typ ändern können, da Sie das Typsystem beschädigen würden. Wenn Sie in C# oder Java new ausführen, wird die neue Methode nur bei statisch verteilten Aufrufen des neuen Typs verwendet. In Javascript sind alle Aufrufe dynamisch und wenn Sie den Typ ändern, wird der Code wahrscheinlich zur Laufzeit brechen (das Erzwingen des Typs könnte jedoch aus praktischen Gründen erlaubt sein, aber nicht erweitern).

class Person {
    walk() { }

    run() {
        this.walk();
        this.walk();
        this.walk();
    }
}

class SuperMan extends Person {
    new walk(destination: string) { } //Even if you write `new` this code will break
}

Also mehr als new sollte das Schlüsselwort assign sein, da dies die einzige typsichere Sache ist, die Sie tun können:

class Person{
    dateOfBirth: Date;
}

class SuperMan extends Person implements ICanFly {
     assign dateOfBirth = new Date();
}

Beachten Sie, dass reassign keinen Sinn ergibt, da Person das Feld nur deklariert, aber keinen Wert darauf setzt.

Das Schreiben assign erscheint überflüssig, es ist jedoch klar, dass wir zuweisen, weil wir auf der anderen Seite ein = haben.

Auch hier löst die this. -Syntax das Problem elegant:

class Person{
    dateOfBirth: Date;
}

class SuperMan extends Person implements ICanFly {
     this.dateOfBirth = new Date();
}

Ich bin mir nicht sicher, wie Sie dies sinnvoll auf Felder anwenden.

Wir sprechen davon, ein Schlüsselwort hinzuzufügen, das nur im Klassenkörper anwendbar ist, Felder können jedoch zu _jedem Zeitpunkt_ während der Lebensdauer der Instanz neu zugewiesen werden.

Mit Typescript können Sie bereits Felder im Klassentext initialisieren:

class Person {
    dateOfBirth : new Date();
}

Es erlaubt Ihnen auch, in der übergeordneten Klasse deklarierte Felder neu zu initialisieren, aber die Situation ist schlecht. Natürlich könnte man den Namen falsch schreiben (ähnliches Problem wie override ) aber der Typ wird auch gelöscht. Dies bedeutet, dass:

class Person {
    fullName: { firstName: string; lastName?: string };
}

class SuperMan extends Person {
    fullName = { firstName: "Clark" };

    bla() {
        this.fullName.lastName; //Error
    }
}

Dieser Code schlägt fehl mit: Property 'lastName' does not exist on type '{ firstName: string;

Wir sprechen über das Hinzufügen eines Schlüsselworts, das nur im Klassentext anwendbar ist, Felder können jedoch jederzeit während der Lebensdauer der Instanz neu zugewiesen werden.

Auch Methoden:

class Person {
    talk() { }
}

class SuperMan extends Person {

    talk() { }

    changeMe(){
        this.talk = () => { };      
    }

    changeMyPrototype() {
        SuperMan.prototype.talk = () => { };
    }
}

@kungfusheep Ich stimme zu, Felder können neu zugewiesen werden, aber auch Methoden des Prototyps.
Der Hauptgrund, warum wir ein Schlüsselwort zum „Überschreiben“ von Feldern benötigen würden, ist, dass Instanzmethoden Felder sind und häufig anstelle von richtigen Prototypmethoden verwendet werden, da sie Lambdas sind und daher den Kontext automatisch binden.

@olmobrutall

Informationen zum Überschreiben von Methoden mit Feldern

Es ist verboten und aus gutem Grund glaube ich (es ist nicht dasselbe, also sollte es nicht kompatibel sein), aber Sie haben Recht, dass dies möglich ist, wenn Sie die Instanz der Klasse direkt manipulieren. Ich weiß nicht genau, warum es erlaubt ist, aber wir sind nicht hier, um zu ändern, wie die Sprache funktioniert, also glaube ich, dass wir uns hier nicht einmischen sollten.

Über Schnittstellen

Klassenschnittstellen beschreiben die Instanzseite einer Klasse, also sowohl Felder als auch Methoden aus dem Prototyp. Ich glaube nicht einmal, dass es einen Unterschied zwischen Instanz- und Prototypmethoden in einer Schnittstelle gibt, ich glaube, Sie können eine Methode als die eine oder andere beschreiben und sie als die eine oder andere implementieren. Also könnte (und sollte) implement auch für Felder gelten.

Über neu

new sollte dieselbe Semantik wie override haben, also können Sie den Typ offensichtlich nicht in etwas ändern, das nicht kompatibel ist. Ich meine, noch einmal, wir ändern nicht, wie die Sprache funktioniert. Ich wollte nur ein separates Schlüsselwort, weil es nicht dieselbe Art der Neudefinition ist. Aber ich stimme zu, dass wir, wie Sie mir gezeigt haben, Felder und Methoden bereits durch die Verwendung von = unterscheiden können, sodass möglicherweise das gleiche Schlüsselwort/die gleiche Syntax ohne Mehrdeutigkeit verwendet werden könnte.

Darüber.

Die vereinheitlichte Feld-/Methodenüberschreibungssyntax sollte jedoch nicht this.method() oder this.field lauten, da sie sehr nach etwas aussieht, das gültiges Javascript sein könnte, obwohl dies nicht der Fall ist. Das Hinzufügen eines Schlüsselworts vor dem Mitglied würde deutlicher machen, dass es sich tatsächlich um eine Typescript-Sache handelt.
this ist aber eine gute Idee, also könnten wir vielleicht den Punkt weglassen und etwas weniger Zweideutiges wie schreiben

class Superman {
    this walk() { }
}

Aber trotzdem sieht es ein bisschen komisch aus. Und es mit implement zu mischen würde etwas fehl am Platz aussehen (weil wir damit einverstanden sind, dass diese this -Syntax nicht mit Interface verwendet wird?), und ich mag das implement / override Duo. Ein override -Modifikator auf Feldern ärgert mich immer noch, aber vielleicht ist es besser, ein drittes mehrdeutiges new -Schlüsselwort zu haben. Da die Zuweisung ( = ) den Unterschied zwischen einer Methode und einem Feld deutlich macht, wäre das für mich jetzt in Ordnung (im Gegensatz zu dem, was ich vor ein paar Stunden gesagt habe).

Ich stimme zu, Felder können neu zugewiesen werden, aber auch Methoden des Prototyps.

Ja, das direkte Modifizieren des Prototyps in TypeScript liegt jedoch weit außerhalb des Herumspielens unter der Haube. Es ist bei weitem nicht so üblich wie das einfache Neuzuweisen einer Eigenschaft für eine Instanz von etwas. Wir verwenden Felder sehr selten als Funktionen über eine Codebasis von mehr als 150.000 Zeilen, daher denke ich, dass es vielleicht subjektiv ist zu sagen, dass es weit verbreitet ist.

Ich stimme den meisten Ihrer anderen Punkte zu, aber ich bin nicht überzeugt (und wie es klingt, Sie auch nicht ...) über die Verwendung des Schlüsselworts new in diesem Zusammenhang.

Wir verwenden Felder sehr selten als Funktionen über eine Codebasis von mehr als 150.000 Zeilen, daher denke ich, dass es vielleicht subjektiv ist zu sagen, dass es weit verbreitet ist.

Ich auch nicht, aber ich muss auf einen @autobind -Dekorateur zurückgreifen, um this in meinen Methoden richtig zu binden, was umständlicher ist, als stattdessen einfach ein Lambda zu schreiben. Und solange Sie keine Vererbung verwenden, funktioniert die Verwendung eines Felds wie erwartet. Ich hatte den Eindruck, dass zumindest in der JS/React-Welt die meisten Leute, die ES6-Klassen verwenden, Pfeilfunktionen als Methoden verwenden.

Und Schnittstellenmethoden können entweder mit einer richtigen Methode oder mit einer Instanzmethode implementiert werden, was nicht hilft, den Unterschied zu verdeutlichen.

Ich glaube, Felder haben die gleichen Probleme wie Methoden, was Überschreibungen betrifft.

class A {
    firstName: string;
    get name() {
        return this.firstName;
    }
}

class B extends A {
    firstname = "Joe" // oops
}

Eine mögliche Problemumgehung besteht hier darin, das Feld im Konstruktor zuzuweisen

class B extends A {
    constructor() {
        this.firstName = "Joe"; // can't go wrong
    }
}

aber das gilt nicht für Schnittstellen. Deshalb glaube ich (und andere hier), dass wir etwas brauchen, um die Präexistenz eines Felds zu überprüfen, wenn es direkt im Klassenkörper deklariert wird. Und es ist kein override . Nun, da ich hier noch ein paar Gedanken trage, glaube ich, dass das Problem mit Feldern genau das gleiche ist wie das Problem mit Schnittstellenmitgliedern. Ich würde sagen, wir brauchen ein Schlüsselwort override für Methoden, die bereits eine Implementierung in einer übergeordneten Klasse haben (Prototypmethoden und vielleicht auch Instanzmethoden), und ein weiteres für Dinge, die zwar definiert sind, aber keine Implementierung haben (non-function Felder enthalten).

Mein neuer Vorschlag wäre, und mit einem vorläufigen Schlüsselwort member (wahrscheinlich nicht die beste Wahl):

interface IBase {
    interfaceField?: string;
    interfaceMethod(): void
}

abstract class Base {
    baseField: number;
    baseMethod() { }
    baseLambda: () => { };
    abstract baseAbstractMethod();
}

class Derived extends Base implements IBase {
    member interfaceField = "Hello";
    member interfaceMethod() { }
    member baseField = 2;
    override baseMethod() { }
    override baseLambda = () => { };
    member baseAbstractMethod() { }
}

Beachten Sie, dass ich das Schlüsselwort member für eine abstrakte Methode wählen würde, da ich gesagt habe, dass override für Dinge mit einer Implementierung wäre, was darauf hinweist, dass wir ein vorhandenes Verhalten ersetzen. Instanzmethoden müssten override verwenden, da es sich um eine spezielle Art von Feld handelt (das vom Compiler bereits als solches erkannt wird, da Methodensignaturen damit implementiert werden können).

Ich denke, Typescript sollte eine zur Kompilierzeit geprüfte Version von JavaScript sein, und wenn Sie TS lernen, sollten Sie auch JS lernen, genauso wie Sie durch das Erlernen von C# eine gute Intuition der CLR bekommen.

Prototypische Vererbung ist ein cooles Konzept von JS und ein ziemlich zentrales, und TS sollte nicht versuchen, es mit Konzepten aus anderen Sprachen zu verstecken, die hier nicht allzu viel Sinn machen.

Die prototypische Vererbung macht keinen Unterschied, ob Sie Methoden oder Felder erben.

Zum Beispiel:

class Rectangle {
       x: number;
       y: number;
       color: string;
}

Rectangle.prototype.color = "black";

Hier legen wir ein einfaches Feld im Prototypobjekt fest, sodass alle Rechtecke standardmäßig schwarz sind, ohne dass ein Instanzfeld dafür vorhanden sein muss.

class BoundingBox {
      override color = "transparent"; // or should be member? 
}

Auch das Schlüsselwort member macht die anderen Mitglieder der Klasse neidisch.

Was wir brauchen, ist eine Syntax, die in einem Klassendeklarationskontext die gleiche Art von Verhalten (Überprüfung der Kompilierzeit/Autovervollständigung/Umbenennung) zulässt, die wir bereits in Objektliteral- oder Elementausdrücken haben.

Vielleicht ist eine bessere Alternative zu this. nur . :

class Derived {
   .interfaceField = "hello";
   .interfaceMethod() {}
   .baseField = 2;
   .baseMethod() {}
   .baseLambda = () => {};
   .baseAbstractMethod(){};

   someNewMethod(){}
   someNewField = 3;
}

Zusammenfassend denke ich nicht, dass das Typesystem nachverfolgen sollte, welche Werte aus dem Prototyp und welche aus der Instanz stammen, da dies ein schwieriges Problem ist, sobald Sie unbedingt auf den Prototyp zugreifen können, und weil es kein Problem beheben wird, solange es geht begrenzen die Ausdruckskraft von JS.

Es gibt also keinen Unterschied zwischen einem Funktionsfeld, einem Pfeilfunktionsfeld und einer Methode, weder im Typsystem noch im generierten Code (wenn this nicht verwendet wird).

Hey Leute, das ist eine interessante Sache, aber speziell in Bezug auf override geht es ziemlich daneben. Ich würde mich über ein neues Diskussionsthema für solche Dinge freuen, es sei denn, wir können diese Kommentare konkreter mit dem ursprünglichen Vorschlag verknüpfen.

Vieles, was Sie hier sagen, ist auch nicht spezifisch für TypeScript, daher kann es auch angebracht sein, einen ESDiscuss-Thread zu starten. Sicherlich haben sie genauso viel über solche Dinge nachgedacht (im Hinblick darauf, was auf dem Prototyp vs. der Instanz passiert).

@olmobrutall
ES6-Klassen verstecken bereits das Prototyp-Zeug, und wie @kungfusheep sagte, ist es nicht gerade normal, direkt damit herumzuspielen.

Es gibt also keinen Unterschied zwischen einem Funktionsfeld, einem Pfeilfunktionsfeld und einer Methode, weder im Typsystem noch im generierten Code (wenn dieser nicht verwendet wird).

Nun, im generierten Code werden Klassenmethoden in den Prototyp eingefügt und alles andere (ohne Präfix static ) in die Instanz eingefügt, was bei der Vererbung einen Unterschied macht.

Wie auch immer, ich stimme jetzt zu, dass wir keine separate Syntax für beide Arten von Methoden haben müssen, aber wenn wir das Schlüsselwort override verwenden würden, sollte es auf Methoden beschränkt sein und wir müssten es finden für den Rest etwas anderes. Ein eindeutiges Schlüsselwort für alles zu haben, könnte nett sein, aber es sollte seine Bedeutung sehr klar machen. override ist klar, aber nur für Methoden. Ihre Punktsyntax, so etwas wie this. ist für meinen Geschmack immer noch zu nah an vorhandenem JS.

@RyanCavanaugh

Es ist ziemlich aus der Tangente in Bezug auf override

Meine Probleme mit dem Überschreiben sind, dass es für Schnittstellenmethoden und -felder nicht allgemein genug ist. Ich sehe drei Möglichkeiten:

  • Verwenden override nur für Methoden der Basisklasse.
  • Verwenden override auch für Schnittstellenmethoden und -felder
  • Betrachten Sie alternative Syntaxen ( member , implement , this. , . , etc...)
  • nichts tun (das wird traurig sein)

Ich denke, diese Entscheidung sollte hier getroffen werden.

Vieles, was Sie sagen, ist wirklich nicht spezifisch für Typescript

Absolut! Wir freuen uns alle über den aktuellen Stand beim Umstapeln, aber nicht über die Typenprüfung / Werkzeugausstattung.

Was auch immer das Schlüsselwort ist, es wird nur Typoskript sein (genauso abstrakt)

@JabX

Was den generierten Code betrifft, haben Sie natürlich Recht. Mein Punkt war, dass Sie so etwas schreiben können:

class Person {
    name: string = "John"; 

    saySomething() {
        return "Hi " + this.name;
    }
}

Wir deklarieren eine Klasse, name geht an die Instanz, während saySomething an den Prototyp geht. Ich kann trotzdem schreiben:

Person.prototype.name = "Unknown"; 

Denn der Typ von Person.prototype ist die ganze Person. Typoskript verfolgt der Einfachheit halber nicht, was wohin in sein Typsystem gehört.

Ein eindeutiges Schlüsselwort für alles zu haben, könnte nett sein, aber es sollte seine Bedeutung sehr klar machen.

Was ich für wichtiger halte und auf die wir uns alle einigen könnten, ist die Semantik:

Modified XXX überprüft, ob das Mitglied bereits in der Basisklasse oder in implementierten Schnittstellen deklariert ist, die normalerweise verwendet werden, um Funktionen aus der Basisklasse zu überschreiben.

Da . oder this. zu fremdartig aussehen und member überflüssig ist, denke ich, dass es wahrscheinlich die beste Wahl ist, override für alle Fälle zu missbrauchen . Das Setzen von Feldern oder das Implementieren von Schnittstellenmethoden ist ohnehin ein Nebenfeature.

Es gibt viele Präzedenzfälle:

  • In C# ist ein static class nicht mehr wirklich eine _Klasse von Objekten_.
  • static -Felder sind nicht _statisch_.
  • virtual Methoden sind ziemlich _konkret_ (VB verwendet Overrideable ).

Es wird so aussehen:

class Person{
    dateOfBirth: Date;

    abstract talk();
    walk(){ //...}
}

interface ICanFly{
    fly?();
    altitude?: number;
}


class SuperMan extends Person implements ICanFly {
     override dateOfBirth = new Date();

     override talk(){/*...*/}
     override walk = () => {/* force 'this' to be captured*/} 

     override  fly() {/*...*/}
     override altitude = 1000; 
}

Können wir uns darauf einigen?

Ich würde lieber ein separates Schlüsselwort implement für alles sehen, was von Schnittstellen kommt (mit Priorität von override , wenn es in beiden enthalten ist), weil es das ist, was es ist: eine Implementierung, keine Überschreibung.
Andernfalls stimme ich zu, dass es am besten ist, override für alles aus der Elternklasse zu missbrauchen.

Aber es gibt keinen semantischen Unterschied zwischen implement und override .

Beide werden ähnliche automatische Vervollständigung / Fehlermeldungen / Transpilation wie JS haben ... es ist nur ein philosophischer Unterschied.

Es lohnt sich wirklich, zwei Schlüsselwörter zu erklären, die Ihnen ein umständlicher Compiler sagt: Error you should use 'override' instead of 'implement' .

Was ist mit diesem Fall:

interface IComparable {
     compare(): number;
} 

class BaseClass implements IComparable {
    implement compare(); 
}

class ChildClass extends BaseClass implements IComparable { //again 
     override compare(); // or implements... 
}

Die Frage ist ... wen interessiert das?.

Außerdem gibt es das implement / implements Problem.

Lass uns einfach override missbrauchen. Ein Konzept, ein Stichwort, das ist das Wichtigste.

Nun, ich habe bereits vorgeschlagen, dass override Vorrang vor implement haben sollte, aber ich war wahrscheinlich nicht deutlich genug.
Ich glaube immer noch nicht, dass das dasselbe ist. Ein weiteres Beispiel: Bei einem obligatorischen Schnittstellenmember ist ein Fehler beim Implementieren ein Compilerfehler, während ein Fehler beim Überschreiben kein Problem darstellt (es sei denn, der Basismember ist abstrakt, das heißt ...).

Ich glaube einfach nicht, dass override für etwas, das nicht in einer übergeordneten Klasse deklariert ist, Sinn machen würde. Aber vielleicht bin ich der einzige, der die Unterscheidung will. Unabhängig davon, welches Schlüsselwort wir letztendlich dafür verwenden, hoffe ich nur, dass wir uns auf etwas einigen und einen strengen Modus bereitstellen können, um seine Verwendung durchzusetzen.

Nun, ich habe bereits vorgeschlagen, dass Override Vorrang vor Implementieren haben sollte, aber ich war wahrscheinlich nicht klar genug.

Sicher, ich wollte nur sagen, dass es vier Fälle gibt. Basisklasse, Schnittstelle, Schnittstelle in Basisklasse, Schnittstelle in Basisklasse neu implementieren.

@JabX

Ich glaube einfach nicht, dass das Überschreiben von etwas, das nicht in einer übergeordneten Klasse deklariert ist, Sinn machen würde. Aber vielleicht bin ich der Einzige, der die Unterscheidung will .

Du bist nicht. Das sind zwei grundlegend unterschiedliche Dinge, und es ist von Vorteil, diese Trennung auf sprachlicher Ebene zu haben.

Ich denke, wenn Schnittstellen in dieser neuen Funktionalität unterstützt werden sollen, dann kann es keine halbgare Lösung sein, die auf override geschraubt wurde. Aus dem gleichen Grund existiert extends als separate Entität zu implements auf Klassenebene, override muss der Reihe nach mit etwas in der Art von implement gepaart werden um dieses Problem zu beheben. Es ist _Funktionalität ersetzen_ vs. _Funktionalität definieren_.

@olmobrutall

Es lohnt sich wirklich, zwei Schlüsselwörter zu erklären, die Ihnen ein umständlicher Compiler mitteilt: Fehler, Sie sollten 'override' anstelle von 'implement' verwenden.

Ich habe das Gefühl, dass ich diese Unterscheidung jetzt ungefähr 4 Mal gemacht habe, aber das ist nicht pedantisch; Das sind wirklich wichtige Informationen!

Betrachten Sie Ihr eigenes Beispiel

interface IComparable {
     compare(): number;
} 

class BaseClass implements IComparable {
    implement compare(); 
}

class ChildClass extends BaseClass implements IComparable { //again 
     override compare(); // or implements... 
}

'oder implementiert ...' ist in diesem Fall eine völlig falsche Annahme. Nur weil Sie dem Compiler gesagt haben, dass Sie dieselbe Schnittstelle wie die von Ihnen erweiterte Basisklasse implementieren möchten, ändert dies nichts an der Tatsache, dass Sie compare bereits implementiert haben.
Wenn Sie implement statt override $ in ChildClass schreiben würden, sollten Sie dankbar sein, dass der Compiler Ihnen dann Ihre falsche Annahme mitteilt, denn es ist eine große Sache, dass Sie unwissentlich löschen würden aus einer zuvor implementierten Methode!

In den Codebasen, für die ich verantwortlich bin, wäre dies ohne Zweifel ein großes Problem; Daher begrüße ich jedes Compiler-Feature, das solche Entwicklerfehler verhindern kann!

@kungfusheep

Wenn Sie in ChildClass implement statt override schreiben würden, sollten Sie dankbar sein, dass der Compiler Ihnen dann Ihre falsche Annahme mitteilt, denn es ist eine große Sache, dass Sie dabei waren, unwissentlich eine zuvor implementierte Methode zu löschen!

Wenn das Überschreiben einer bereits implementierten Methode eine so wichtige Entscheidung ist, dann sollte implement auch für abstrakte Methoden verwendet werden. Wenn Sie eine Methode von abstract in virtual oder umgekehrt ändern, sollten Sie alle implementierten Versionen überprüfen (und ändern), um in Erwägung zu ziehen, super aufzurufen oder die Methode einfach zu entfernen.

In der Praxis war dies in C# noch nie ein Problem.

Ich stimme zu, override für implementierte Methoden und implements für nicht implementierte Methoden (Abstract / Interface).

Es könnte für Abstracts verwendet werden, ja.

In C# gab es das „optionale“ Schnittstellenszenario nicht, daher wurde es vielleicht nicht so sehr als Problem angesehen.

Aber mein Punkt ist, dass wir in C# override Methoden implementiert und nicht implementiert haben und ich noch nie von jemandem gehört habe, der sich beschwert hat.

Ich denke nicht, dass es wirklich ein Problem ist, das es verdient, die Funktion mindestens zweimal schwieriger zu erklären.

Nun, der einzige Grund, warum wir ein Schlüsselwort implement brauchen, ist, dass Schnittstellenmember optional sein können, während dies in C# (und wahrscheinlich auch in den meisten OO-Sprachen) nicht möglich ist. Es gibt dort kein Schlüsselwort dafür, weil Sie eine Schnittstelle nicht vollständig implementieren können, also kein Problem. Stützen Sie Ihr Argument nicht zu sehr auf „in C# ist es dasselbe“, da wir hier unterschiedliche Probleme haben.

override wird für Methoden der _Basisklasse_ verwendet, unabhängig davon, ob sie abstrakt sind oder nicht. Ich nehme an, wir sollten hier dasselbe tun ( override statt implement für abstrakte Methoden), weil ich glaube, dass die Unterscheidung auf dem Ursprung der Methode (Klasse oder Schnittstelle) statt auf der Existenz liegen sollte der Umsetzung. Und wenn Sie sich in Zukunft dafür entscheiden, Ihrer abstrakten Methode eine Standardimplementierung zu geben, müssen Sie nicht Ihren Code (oder schlimmer noch den Code anderer, die Ihre Klasse verwenden) durchgehen, um die Schlüsselwörter zu ersetzen.

Meine Hauptfrage ist jetzt, sollten wir ein striktes override / implement Flag haben (ich will das unbedingt), sollten wir das Schlüsselwort implement für obligatorische Interface-Member erzwingen? Weil es nicht viel hilft (Sie können nicht umhin, diese zu implementieren, weil es sonst nicht kompiliert wird) und zu viel unnötiger Ausführlichkeit führen könnte. Aber andererseits könnte es trügerisch sein, die implement auf einigen Schnittstellenmitgliedern zu haben, aber nicht auf allen.

@JabX Ich persönlich möchte eine Warnung, wenn eine Basisklasse eine Implementierung einer abstrakten Methode hinzufügt.

Ich bin jedoch nicht zu 100 % von der Idee eines Schlüsselworts für Arbeitsgeräte überzeugt. Der Compiler warnt Sie bereits, wenn Sie etwas nicht implementiert haben. Der einzige Ort, an dem es wirklich hilfreich ist, sind optionale Methoden.

Und überhaupt, es hat nichts damit zu tun, warum ich in diesem Thread bin. Ich habe nur nach einer Möglichkeit gesucht, eine Funktion als Überschreibung einer übergeordneten Klasse anzugeben.

Meine Hauptfrage ist jetzt, sollten wir ein striktes Override/Implement-Flag haben (ich möchte das unbedingt), sollten wir das Implement-Schlüsselwort für obligatorische Schnittstellenmitglieder erzwingen?

Ich denke, es nicht zu schreiben, sollte eine Warnung sein.

Der einzige Ort, an dem es wirklich hilfreich ist, sind optionale Methoden.

Ja, das ist irgendwie der springende Punkt hier.

Ohne dies ist es ein sehr häufiger Fehler bei der Tippfehlermethode, die Sie überschreiben möchten, aber tatsächlich erstellen Sie eine neue Methode. Die Einführung eines solchen Schlüsselworts ist nur eine Möglichkeit, auf die Absicht hinzuweisen, die Funktion zu überschreiben.

Das könnte eine Warnung oder was auch immer sein, aber derzeit ist es sehr schmerzhaft.

Ich füge der UML-Klassenansicht in alm.tools eine Override-Anmerkung hinzu :rose:

image

Referenzen https://github.com/alm-tools/alm/issues/84

Ich habe auch einen Rinnenindikator für Klassenmitglieder hinzugefügt, die ein Basisklassenmitglied in alm überschreiben.

overrides

Referenzen https://github.com/alm-tools/alm/issues/111

Das Akzeptieren von PRs für eine bereichsbezogene Lösung erzielt unserer Meinung nach den größten Wert mit der geringsten Komplexität:

  • Neues Schlüsselwort override gilt für Klassenmethoden- und Eigenschaftsdeklarationen (einschließlich get/set)

    • Alle Signaturen (einschließlich der Implementierung) müssen override , wenn dies der Fall ist

    • Sowohl get als auch set müssen mit override gekennzeichnet sein, wenn beides der Fall ist

  • Es ist ein Fehler, override zu haben, wenn es keine Eigenschaft/Methode der Basisklasse mit diesem Namen gibt
  • Der neue Befehlszeilenschalter --noImplicitOverride (hier können Sie gerne den Namen bikeshed) macht override obligatorisch für Dinge, die außer Kraft gesetzt werden

    • Dieser Schalter hat keine Wirkung in Ambient-Kontexten (dh declare class oder .d.ts-Dateien)

Derzeit nicht im Geltungsbereich:

  • Signaturen erben
  • Kontextabhängige Eingabe von Parametern oder Initialisierern (habe dies früher versucht und es war eine Katastrophe)
  • Entsprechendes Schlüsselwort zur Angabe von „Ich implementiere ein Schnittstellenmitglied mit dieser Deklaration“

@RyanCavanaugh
Ich versuche gerade, dies zu implementieren (gerade erst begonnen, und ich würde jede Hilfe dazu begrüßen, insbesondere zum Testen), aber ich bin mir nicht sicher, ob ich verstehe, was dahinter steckt

Alle Signaturen (einschließlich der Implementierung) müssen override , wenn dies der Fall ist

Da Sie nicht mehrere Signaturen einer Methode in einer Klasse haben können, sprechen Sie über den Vererbungskontext? Meinst du das (ohne das Flag, das die Verwendung von override erzwingt, sonst ist es offensichtlich ein Fehler)

class A {
    method() {}
}

class B extends A {
    override method() {}
}

class C extends B {
    method() {} 
}

sollte einen Fehler in Klasse C ausgeben, weil ich nicht override vor method geschrieben habe?

Wenn das der Fall ist, bin ich mir nicht sicher, warum wir das durchsetzen wollen. Und sollte es umgekehrt auch funktionieren? Einen Fehler in Klasse B ausgeben, wenn ich override in C und nicht in B angegeben habe?

Ja, ich denke, es gilt für die Signaturen des Vererbungsbaums.
Und ich glaube nicht, dass es umgekehrt funktionieren wird, es sollte mit der Durchsetzung der Außerkraftsetzungsregel beginnen, sobald sie auf einer Ebene in der Hierarchie verwendet wird. In einigen Fällen kann die Entscheidung zum Anwenden des Überschreibens auf eine Klasse getroffen werden, die von einer Klasse abgeleitet ist, die nicht dem Entwickler gehört. Sobald es also verwendet wird, sollte es nur für alle abgeleiteten Klassen gelten.

Ich habe noch eine Frage bzgl

Sowohl get als auch set müssen mit override gekennzeichnet sein, wenn beides der Fall ist

Was passiert da, wenn meine Elternklasse nur einen Getter definiert und ich in meiner abgeleiteten Klasse den Setter definieren möchte? Wenn ich dieser Regel folge, bedeutet dies, dass mein Setter als Überschreibung definiert werden müsste, aber es gibt keinen Setter in der übergeordneten Klasse, und es sollte ein Fehler sein, etwas zu überschreiben, das nicht existiert. Nun, in der Praxis (in meiner aktuellen naiven Implementierung) gibt es keinen Widerspruch, da der Getter und der Setter für dieselbe Eigenschaft stehen.
Ich könnte sogar etwas schreiben wie:

class A {
    get value() { return 1; }
}

class B extends 1 {
   override set value(v: number) {}
}

Was für mich einfach falsch aussieht.

Da Sie nicht mehrere Signaturen einer Methode in einer Klasse haben können

Sie können mehrere Überladungssignaturen für dieselbe Methode haben:

class Base { bar(): { } }

class Foo extends Base {
  // Must write 'override' on each signature
  override bar(s: string): void;
  override bar(s?: number): void;
  override bar(s: string|number) { }
}

Ich denke, Ihre Intuition in Bezug auf die anderen Fälle ist richtig.

In Bezug auf Getter und Setter denke ich, dass override nur für den Property-Slot gilt. Das Schreiben eines überschreibenden Setters ohne einen entsprechenden Getter ist eindeutig äußerst verdächtig, aber in Gegenwart von override ist es nicht mehr oder weniger verdächtig. Da wir nicht immer wissen, wie die Basisklasse implementiert wurde, lautet die Regel meiner Meinung nach einfach, dass ein override Getter oder Setter _irgendeine_ entsprechende Basisklasseneigenschaft haben muss, und zwar, wenn Sie override get { sagen , jede set Deklaration muss auch override haben (und umgekehrt)

Oh ok, ich wusste nicht, dass wir Methodensignaturen in solchen Klassen schreiben können, das ist ziemlich nett.

Ich denke, virtual würde auch Sinn machen.
Alle Schlüsselwörter: virtual , override , final würden tatsächlich einen großen Unterschied machen, besonders wenn Klassen umgestaltet werden.
Ich verwende stark Vererbung, es ist jetzt so einfach, die Methode "aus Versehen" zu überschreiben.
virtual würde auch die Intelligenz verbessern, da Sie nur abstrakte/virtuelle Methoden nach dem Schlüsselwort überschreiben könnten.

Bitte, bitte priorisieren Sie diese. Danke!

Ich stimme @pankleks zu - ich verwende häufig Vererbung, und es fühlt sich einfach falsch an, eine Funktion zu überschreiben, ohne etwas direkt anzugeben.

"virtuell", "override" und "final" wären perfekt.

Dies ist ein wichtiges Thema für mich.

@RyanCavanaugh

Kontextabhängige Eingabe von Parametern oder Initialisierern (habe dies früher versucht und es war eine Katastrophe)

Können Sie das näher erläutern? Ich habe dieses Problem tatsächlich gefunden, als ich versuchte herauszufinden, warum TS den Typ meiner Parameter bei Methodenüberschreibungen nicht ableitet, was mich überraschte. Ich sehe nicht, wann eine nicht identische Parameterliste beim Überschreiben korrekt wäre oder ein Rückgabetyp, der nicht kompatibel ist.

@Pankleks

Ich denke, virtual würde auch Sinn machen.

Von Natur aus ist in JS alles "virtuell". Unterstützung für final würde IMHO ausreichen: Wenn Sie eine Methode (oder Eigenschaft) haben, die nicht überschrieben werden darf, können Sie sie als solche markieren, um einen Compilerfehler auszulösen, wenn dies verletzt wurde. Keine Notwendigkeit für virtual , oder? Dies wird jedoch in Ausgabe Nr. 1534 verfolgt.

@avonwyss hier gibt es zwei Probleme.

Als wir versuchten, _property initializers_ kontextbezogen einzugeben, stießen wir auf einige Probleme, die unter https://github.com/Microsoft/TypeScript/pull/6118#issuecomment -216595207 beschrieben werden. Wir glauben, dass wir bei #10570 einen neuen, erfolgreicheren Ansatz haben

Methodendeklarationen sind ein anderer Wachsball und da müssen wir noch einige andere Dinge herausfinden, wie zum Beispiel, was hier passieren sollte:

declare class Base {
  method(x: string): string[];
  method(x: number, count: number): number[];
}
class Derived extends Base {
  method(x) { // x: ???
    return x;
  }
}

Wir können einfach entscheiden, dass nur Single-Signatur-Methoden Parametertypen von der Basis erhalten. Aber das wird nicht jeden glücklich machen und löst nicht wirklich das häufige Problem "Ich möchte, dass meine abgeleitete Klasse zur Methode die gleichen _Signaturen_ hat, aber nur eine andere _Implementierung_ bereitstellt".

Nun, der Typ x wäre in diesem Fall string | number , aber ich verstehe, dass es ziemlich schwierig sein könnte, dies konsistent herauszufinden.

Aber Sie möchten wahrscheinlich nicht, dass der _extern sichtbare_ Typ von x string | number ist - vorausgesetzt, Derived hat die gleiche Semantik wie Base , würde es Der Aufruf mit (3) oder ('foo', 3) ist nicht zulässig

Hm ja, das habe ich übersehen.

Ich sehe, Sie haben vorgeschlagen, es auf Single-Signature-Methoden zu beschränken, aber ich glaube, es könnte "einfach" auf "Matching-Signature" -Methoden erweitert werden, wie in Ihrem Beispiel:

declare class Base {
  method(x: string): string[];
  method(x: number, count: number): number[];
}
class Derived extends Base {
  method(x, count) { // x: number, count: number
    return [];
  }
}

weil es nur eine Signatur in der Basisklasse gibt, die übereinstimmt.

Das wäre viel besser als das, was wir haben (nichts), und ich glaube nicht, dass es _so_ schwer wäre, dies zu tun? (immer noch weit außerhalb des Geltungsbereichs für dieses Problem und wahrscheinlich mehr Arbeit)

@RyanCavanaugh Danke für die Antwort. Wie bereits erwähnt, hoffe ich für override , dass es das Problem des Parameters (und des Rückgabetyps) lösen würde. Aber ich sehe Ihr Beispiel nicht als problematisch an, da Überladungen immer eine einzige "private" Implementierung haben müssen, die mit allen Überladungen kompatibel ist (weshalb der Vorschlag von @JabX konzeptionell nicht funktionieren würde).
Also, wir hätten so etwas:

declare class Base {
  method(x: string): string[];
  method(x: number, count: number): number[];
  // implies: method(x: string|number, count?: number): string[]|number[]
  // or fancier: method<T extends string|number>(x: T, count?: number): T[]
}
class Derived extends Base {
  override method(x, count?) { // may only be called like the method on Base
    return [x];
  }
}

Diese Logik existiert bereits und die Überschreibung wäre offensichtlich nur für die "private" Implementierungsmethode verfügbar, nicht für die eindeutigen Überschreibungen (so wie Sie sowieso keine einzelne Überladung explizit implementieren können). Ich sehe hier also kein Problem - oder habe ich etwas übersehen?

Methoden, die überschrieben werden sollen, haben normalerweise sowieso keine Überladungen, also wäre es vollkommen in Ordnung und nützlich, den einfachen Ausweg zu nehmen und die Parameter- und Rückgabetypen nicht abzuleiten, wenn Überladungen vorhanden sind. Selbst wenn das Verhalten beim Überschreiben überladener Methoden so wäre, wie es ist, würden Sie beim Ausführen im --no-implicit-any -Modus einen Fehler erhalten und Sie müssten kompatible Typen angeben, was wie ein perfekter Ansatz erscheint.

Mir fehlt wahrscheinlich etwas, aber in Javascript (und damit Typescript) können Sie nicht 2 Methoden mit demselben Namen haben - auch wenn sie unterschiedliche Signaturen haben?

In C# hättest du das tun können

public string method(string blah) {};
public int method(int blah) {};

Aber hier können Sie niemals 2 Funktionen mit demselben Namen haben, egal was Sie mit den Signaturen machen ... also wäre sowieso keines Ihrer Beispiele möglich, oder? Es sei denn, wir sprechen von mehreren Erweiterungen in derselben Klasse ... aber das zeigen Ihre Beispiele nicht, also wahrscheinlich nicht? Ist das kein Thema?

Im Moment verwende ich Vererbung, und es ist sehr frustrierend ... Ich muss Kommentare zu Funktionen einfügen, um anzuzeigen, dass sie virtuell sind (dh ich überschreibe sie irgendwo) oder außer Kraft setzen (dh, dass diese Funktion überschreibt eine zugrunde liegende Funktion). Super nervig und chaotisch! Und unsicher!

@sam-s4s Ja, es ist möglich, mehrere logische Überladungssignaturen für eine Methode zu deklarieren, aber sie enden alle in derselben einzelnen Implementierung (die dann anhand der übergebenen Parameter herausfinden muss, welche "Überladung" aufgerufen wird). Dies wird von vielen JS-Frameworks stark verwendet, zum Beispiel jQuery, wo $(function) eine fertige Funktion hinzufügt, aber $(string) das DOM nach Selektor durchsucht.

Siehe hier für die Dokumentation: https://www.typescriptlang.org/docs/handbook/functions.html#overloads

@avonwyss Ah ja, Deklarationen, aber keine Implementierungen, das macht Sinn :) Danke

Das hat sich viel zu lange hingezogen, und ich finde es ärgerlich, dass ich diesen ganzen aufgeblähten Thread lesen muss, um überhaupt herauszufinden, warum TS immer noch nicht diese grundlegende Behauptung zur Kompilierzeit hat.

Wo befinden wir uns entweder bei (a) einem override -Schlüsselwort oder (b) einer @override -jsdoc-Anmerkung, die den Compiler darüber informiert, „eine Methode mit demselben Namen und derselben Typsignatur MUSS in einer Oberklasse vorhanden sein.“ ?

Deutet insbesondere der Kommentar in https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -224776519 (8. Juni 2016) von @RyanCavanaugh darauf hin, dass der nächste Schritt eine (Community-)PR ist?

class Bar extends Foo {
  /**
   * <strong i="12">@override</strong>
   */
  public toString(): string {
     // ... 
  }

  override public toString(): string {
    // ...
  }
}

Deutet insbesondere der Kommentar in #2000 (Kommentar) (8. Juni 2016) von @RyanCavanaugh darauf hin, dass der nächste Schritt eine (Community-)PR ist?

@pcj Richtig

Ich habe dazu unter #13217 eine PR gestartet. Alle anderen, die an dieser Funktionalität interessiert sind, sind herzlich eingeladen, sich zu beteiligen und/oder Vorschläge zu machen. Danke!

Ich würde das gleiche wie für Java mit einem Dekorateur bevorzugen

<strong i="6">@Override</strong>
public toString(): string {
   // ...
}

@Chris2011 Stimmen Sie zu, das kommt Ihnen bekannt vor, aber dies würde als Dekorateur bedeuten, dass @Override zur Laufzeit und nicht zur Kompilierzeit ausgewertet würde. Ich plane Javadoc /** <strong i="7">@override</strong> */ in einem separaten PR, sobald #13217 Schlüsselwort überschreiben ( public override toString() ) in der Überprüfung voranschreitet.

Ich versuche, dem Gespräch in diesem Thread zu folgen. Umfasst dies statische Funktionen? Ich sehe keinen Grund, warum wir das Überschreiben statischer Funktionen nicht auch in abgeleiteten Klassen mit völlig anderen Signaturen zulassen könnten. Dies wird in ES6 unterstützt. Wenn class A { } ; A.create = function (){} dann class B extends A { } ; B.create = function (x,y) { } war, wird das Aufrufen A.create() nicht B.create() anrufen und Probleme verursachen. Aus diesem Grund (und um die Möglichkeit zu schaffen, denselben Funktionsnamen für Typen wie Factory-Funktionen zu verwenden), sollten Sie auch das Überschreiben der Signatur von statischen Basisfunktionen zulassen. Es macht nichts kaputt und fügt die Möglichkeit hinzu, ein paar nette Sachen zu machen (insbesondere für Game-Engine-Frameworks, wo "neues" alles wirklich ein Übel ist, wenn es die ganze Zeit verwendet wird, ohne aus einem Cache von Objekten zu ziehen, um GC-Freezes zu reduzieren). Da aufrufbare Konstruktoren unter ES6 nicht unterstützt werden, ist das Erstellen einer gemeinsamen Namenskonvention für Methoden auf Typen die einzige andere Option. Dies erfordert jedoch derzeit, dass die abgeleitete statische Funktion eine Überladung der statischen Basisfunktionssignatur zusammen mit ihrer eigenen anzeigt, was für eine Fabrikfunktion für einen Typ, der nur mit DIESEM Typ befasst ist, nicht nützlich ist. :/ In der Zwischenzeit habe ich nur die Möglichkeit, Benutzer meines Frameworks zu zwingen, alle statischen Funktionssignaturen der Basishierarchie (z. B. SomeType.create() ) auf ihren abgeleiteten Typen zu duplizieren usw .

Hier ist ein Beispiel für die "Albernheit", von der ich spreche (die funktioniert, aber in einem erweiterbaren Rahmen nicht etwas, auf das ich stolz wäre):

class A {
    static create(s: string) {
        var inst: A;
        /* new or from cache */
        inst.init(s);
    }
    protected init(s: string) { }
}

class B extends A {
    static create(s: string);
    static create(n: number);
    static create(n:any) {
        var inst: B;
        /* new or from cache */
        inst.init(n);
    }
    protected init(s: string);
    protected init(n: number);
    protected init(n: any) {
        super.init(n.toString());
    }
}

class C extends B {
    static create(s: string)
    static create(n: number)
    static create(b: boolean)
    static create(b: any) {
        var inst: C;
        /* new or from cache */
        inst.init(b);
    }
    protected init(s: string);
    protected init(n: number);
    protected init(b: boolean);
    protected  init(b: any) {
        super.init(b ? 0 : 1);
    }
}

(https://goo.gl/G01Aku)

Das wäre viel schöner gewesen:

class A {
    static create(s: string) {
        var inst: A;
        /* new or from cache */
        inst.init(s);
    }
    protected init(s: string) { }
}

class B extends A {
    new static create(n:number) {
        var inst: B;
        /* new or from cache */
        inst.init(n);
    }
    new protected init(n: number) {
        super.init(n.toString());
    }
}

class C extends B {
    new static create(b: boolean) {
        var inst: C;
        /* new or from cache */
        inst.init(b);
    }
    new protected  init(b: boolean) {
        super.init(b ? 0 : 1);
    }
}

@rjamesnw Sie könnten an polymorphem „this“ für statische Mitglieder interessiert sein, die einen Vertrag für Factory-Funktionen haben, wird in den Kommentaren als primärer Anwendungsfall diskutiert.

Um also noch einmal ein Gefühl zu wiederholen, das @pcj vor über einem Jahr zum Ausdruck gebracht hat ( Kommentar ), ist es verwirrend, so viele PRs und Kommentare hier und anderswo lesen zu müssen, um festzustellen, wo sich diese Feature-Anfrage befindet.

Es scheint, als wäre #13217 so nah dran gewesen, dann von @DanielRosenwasser und Co. wieder abgeschossen worden, als ein Feature, das möglicherweise nicht in die Sprache passt, und scheinbar wieder in die kreisförmige Konversation hier zu diesem Thema eingetreten ist, ob es getan werden sollte oder nicht. Vielleicht wird dieses 'Design-Zeit'-Dekorateur-Ding (#2900) es lösen, vielleicht auch nicht? Es wäre schön zu wissen.

Hier sind ein paar weitere Mängel des Runtime-Decorator-Ansatzes, die vor ein paar Jahren gepostet wurden und die ich nicht erwähnt gesehen habe:

  • Es funktioniert nicht mit Eigenschaften, da sie nicht Teil des Prototyps sind
  • Eigenschaften und Accessoren können sich (irgendwie...) gegenseitig überschreiben, was ebenfalls nicht funktioniert
  • Wir haben jetzt abstrakte Klassen, aber da abstrakte Dinge zur Laufzeit nicht wirklich existieren, kann es unmöglich mit ihnen arbeiten

Wenn die einzige Einschränkung wäre, dass die Überprüfungen bei der Klasseninitialisierung erfolgten, könnte ich sie wahrscheinlich als vorübergehende Maßnahme akzeptieren, aber so wie es ist, gibt es einfach zu viele Einschränkungen.

Ehrlich gesagt kann ich nicht glauben, dass diese Funktion über 3 Jahre, seit ich das Problem zum ersten Mal geloggt habe, immer noch so umstritten ist. Jede "ernsthafte" objektorientierte Sprache unterstützt dieses Schlüsselwort, C#, F#, C++... Sie nennen es.

Sie können den ganzen Tag über Hypothesen darüber diskutieren, warum Javascript anders ist und einen anderen Ansatz erfordert. Aber aus praktischer Sicht, die täglich in einer sehr großen Typescript-Codebasis arbeitet, kann ich Ihnen sagen, dass das Überschreiben einen großen Unterschied für die Lesbarkeit und Wartung des Codes machen würde. Es würde auch eine ganze Klasse von Fehlern beseitigen, die darauf zurückzuführen sind, dass abgeleitete Klassen versehentlich Basisklassenmethoden mit kompatiblen, aber geringfügig unterschiedlichen Signaturen überschreiben.

Ich würde wirklich gerne eine richtige Implementierung von virtual / override / final sehen. Ich würde es die ganze Zeit verwenden, und es würde den Code so viel lesbarer und weniger fehleranfällig machen. Ich habe das Gefühl, dass dies ein wichtiges Feature ist.

Einverstanden. Es ist frustrierend zu sehen, wie relativ obskure / Grenzfälle hinzugefügt werden, während etwas so ... Grundlegendes abgelehnt wird. Können wir irgendetwas tun, um darauf hinzuwirken?

Kommt schon Leute! Wenn es so viele Kommentare und über drei Jahre gibt, warum wird es nicht bereits implementiert?

Komm schon :joy_cat:

Bitte seien Sie konstruktiv und spezifisch; wir brauchen nicht Dutzende von BITTE SCHON SCHON Kommentaren

Aber Leute, bitte seid auch auf eurer Seite konkret.

Offensichtlich ist die Community sehr an dieser Funktion interessiert, und das TS-Team gibt uns keine Einzelheiten über die Zukunft dieser Funktion.

Ich persönlich stimme @armandn voll und ganz zu, die jüngsten Versionen von TS bringen relativ selten genutzte Funktionen, während so etwas abgehalten wird.

Wenn Sie dies nicht vorhaben, sagen Sie es uns einfach. Teilen Sie uns andernfalls bitte mit, wie die Community helfen kann.

Hier nur eine Zeitleiste, da es mehr Kommentare gibt, als GitHub bereit ist, beim Laden anzuzeigen:

Es ist nicht so, dass wir hier nicht Rücksicht nehmen. Das ist so nah, wie Features kommen, und wir haben unsere eigenen Feature-Ideen aus ähnlichen Gründen verworfen – siehe #24423 für ein aktuelles Beispiel.

Wir wollen die Sprache wirklich absichtlich erweitern. Dies braucht Zeit, und Sie sollten damit rechnen, geduldig zu sein. Im Vergleich dazu ist C++ älter als viele Leute in diesem Thread ; TypeScript ist noch nicht alt genug, um ohne Aufsicht von Erwachsenen zu Hause zu sein. Sobald wir der Sprache etwas hinzugefügt haben, können wir es nicht wieder entfernen, daher muss jede Ergänzung sorgfältig gegen ihre Vor- und Nachteile abgewogen werden. Ich spreche aus Erfahrung, dass Funktionen, nach denen die Leute SCHREIEN wollten (Erweiterungsmethoden, ich sehe Sie an), unsere Fähigkeit zerstört hätten, die Sprache weiterzuentwickeln (keine Schnittmengentypen, keine Vereinigungstypen, keine bedingten Typen), wenn wir sie nur deshalb implementiert hätten Es gab viele GitHub-Kommentare. Ich sage nicht, dass es gefährlich ist, override hinzuzufügen, nur dass wir dies immer mit dem absoluten Maximum an Vorsicht angehen.

Wenn ich jetzt die Kernprobleme mit override zusammenfassen müsste:

  • Es lässt Sie nichts tun, was Sie heute mit einem Dekorateur nicht tun können (also ist es "nicht neu" in Bezug auf "Dinge, die Sie erreichen können").
  • Die Semantik kann nicht genau mit override in anderen Sprachen übereinstimmen (erhöht die kognitive Belastung)
  • Ohne ein neues Befehlszeilen-Flag "leuchtet" es nicht zu 100 % auf, und es wäre ein Flag, das wir wahrscheinlich unter strict haben möchten , aber realistischerweise nicht könnten, weil es zu groß wäre eine bahnbrechende Änderung (verringert den gelieferten Wert). Und alles, was ein neues Befehlszeilen-Flag impliziert, ist eine weitere Verdoppelung des Konfigurationsraums, die wir ernsthaft abwägen müssen, denn Sie können etwas nur so oft verdoppeln, bevor es Ihr gesamtes mentales Budget für zukünftige Entscheidungen aufbraucht
  • Ziemlich starke interne Meinungsverschiedenheit hier darüber, ob override für die Implementierung eines Schnittstellenmitglieds gelten kann (verringert den wahrgenommenen Wert, da die Erwartungen für einen Teil der Menschen nicht mit der Realität übereinstimmen)

Der absolute Vorteil hier ist, dass Sie a) angeben können, dass Sie etwas überschreiben (was Sie heute mit einem Kommentar tun könnten), b) einen Rechtschreibfehler abfangen, bei dem Ihnen die automatische Vervollständigung ohnehin hätte helfen sollen, und c) mit einem neuen Flag, Fangen Sie Stellen, an denen Sie "vergessen" haben, ein Schlüsselwort einzugeben, und dies jetzt tun müssen (eine einfache mechanische Aufgabe, bei der es unwahrscheinlich ist, dass echte Fehler gefunden werden). Ich verstehe, dass b) extrem frustrierend ist, aber auch hier müssen wir die Komplexitätsgrenze erreichen.

Wenn Sie am Ende des Tages glauben, dass JS-Klassen durch ein override -Schlüsselwort enorm geholfen würden, dann wäre es ein guter Anfang, einen TC39-Vorschlag zu unterstützen, es zur Kernlaufzeit hinzuzufügen.

Es lässt Sie nichts tun, was Sie heute mit einem Dekorateur nicht tun können (also ist es "nicht neu" in Bezug auf "Dinge, die Sie erreichen können").

Vielleicht verstehe ich das falsch, aber es gibt ziemlich wichtige Dinge, die ein Dekorateur nicht tun kann, was das Schlüsselwort könnte. Ich habe einige in https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -389393397 erwähnt. Korrigieren Sie mich auf jeden Fall, wenn diese Dinge möglich sind, da ich gerne den Decorator-Ansatz verwenden würde, aber nicht abstrakte Methoden nur ein kleiner Teil meines Anwendungsfalls für diese Funktion sind.

Ein weiterer nicht erwähnter Vorteil ist, dass ein Override-Schlüsselwort das Ändern von etwas in einer Basisklasse viel einfacher machen würde. Das Ändern eines Namens oder das Aufteilen von etwas in zwei Teile usw. ist schwierig zu wissen, dass alle abgeleiteten Klassen problemlos kompiliert werden, ohne dass sie entsprechend aktualisiert werden, aber wahrscheinlich zur Laufzeit fehlschlagen. Wenn man bedenkt, dass keine endgültigen/versiegelten/internen Schlüsselwörter verfügbar sind, könnte fast jede exportierte Klasse irgendwo abgeleitet werden, was das Ändern von fast allem, was nicht privat ist, viel riskanter macht, als es mit verfügbarem Überschreiben wäre.

Mein Eindruck ist, dass eine TSLint-Regel hier bei weitem der reibungsloseste Weg nach vorne wäre. @kungfusheep erwähnte die Idee kurz in https://github.com/Microsoft/TypeScript/issues/2000#issuecomment -192502734 , aber ich sehe keine Folgemaßnahmen. Ist jemand anderem eine TSLint-Implementierung dieser Funktion bekannt? Wenn nicht, werde ich wahrscheinlich bei Gelegenheit anfangen, einen zu hacken. 😄

Mein aktueller Gedanke ist, dass es nur ein Kommentar sein wird, // @override , genau wie // @ts-ignore und verschiedene andere kommentarbasierte Direktiven, die ich gesehen habe. Ich persönlich würde vor Dekoratoren lieber zurückschrecken, da sie (in ihrer aktuellen Form) eine Laufzeitsemantik haben und da sie immer noch Stufe 2 sind und standardmäßig deaktiviert sind.

Mit etwas Glück wäre eine benutzerdefinierte TSLint-Regel eine 90%ige Lösung, der es nur wirklich an Ästhetik mangelt, und natürlich könnte sie sich weiterentwickeln, ohne sich auf Details der Sprache festlegen zu müssen. Mit typbewussten Regeln, tslint-language-service und Auto-Fixing gibt es aus Entwicklersicht wirklich keinen großen Unterschied zwischen einem TSLint-Fehler und einem eingebauten TS-Fehler. Meine Hoffnung ist, dass ein TSLint-Plugin (oder mehrere konkurrierende Plugins) der Community etwas Erfahrung und eine Chance geben würde, sich mehr auf die beste Semantik zu einigen. Dann könnte es vielleicht als TSLint-Kernregel hinzugefügt werden, und vielleicht würde das genug Klarheit und Motivation bieten, um den ästhetischen Vorteil eines override -Schlüsselworts in der Kernsprache zu rechtfertigen.

Hier einfach mal um die Ecke denken. Wir haben hier zwei verschiedene Szenarien. C# (nur als Beispiel) verwendet virtual und override , da Methoden standardmäßig NICHT virtuell sind. In JavaScript sind alle Funktionen einer Klasse standardmäßig (von Natur aus) virtual . Ist es dann nicht sinnvoller, den Prozess umzukehren und stattdessen einen Modifikator vom Typ nooverride zu haben? Natürlich könnten die Leute es immer noch erzwingen, aber zumindest ist es dazu da, eine Konvention durchzusetzen, dass einige Funktionen in Basisklassen nicht berührt werden sollten. Auch hier denke ich nur außerhalb der Norm. ;) Es kann auch weniger Breaking Change sein.

Ist es dann nicht sinnvoller, den Prozess umzukehren und stattdessen einen Modifizierer vom Typ nooverride zu haben?

Ich mag Ihre Denkweise, und ich denke, wonach Sie suchen, ist endgültig .

Aber was ist mit readonly ? Ich glaube, das Überschreiben einer Methode in JS bedeutet tatsächlich, dass die Elternmethode während der Instanziierung (wenn die Prototypkette angewendet wird) durch die Kindmethode ersetzt wird. In diesem Fall ist es sehr sinnvoll, eine readonly -Methode zu haben, die bedeutet: "Es ist für alle sichtbar, aber ich möchte nicht, dass es jemand ändert, sei es durch Vererbung oder dynamisch nach der Instanziierung". Es ist bereits für Mitglieder implementiert, warum nicht auch für Methoden?

Gibt es dafür schon einen Vorschlag? Wenn nicht, könnte es sich lohnen, nach einer Alternative zum Überschreiben zu suchen ...

_edit:_ stellt sich heraus, dass Sie ein schreibgeschütztes Mitglied überschreiben können, sodass dieses ganze Argument zusammenbricht.

Vielleicht ist das Zulassen private -Klassenfunktionen eine weitere Option?

Bearbeiten: Ich dachte, "anstatt es endgültig zu markieren", aber ich muss im Halbschlaf gewesen sein (offensichtlich bedeutet "endgültig" öffentlich, kann aber nicht überschrieben werden), LOL; egal.

@rjamesnw Sie können Klassenfunktionen bereits als öffentlich, geschützt, privat definieren.

Ich denke nicht, dass nur "final" eine Lösung wäre. Das Problem ist, dass Leute versehentlich eine neue Funktion in einer Klasse erstellen können, die von einer Basisklasse mit diesem bereits verwendeten Namen erbt, und dann werden Sie Dinge im Stillen kaputt machen, ohne zu wissen, warum. Es ist mir ein paar Mal passiert, sehr frustrierend, da Sie oft keine Fehler bekommen und seltsame Dinge einfach passieren (oder nicht) ...

Ich denke also, wir sehen uns wirklich einen neuen tsconfig.json-Eintrag an, der den Compiler dazu zwingt, einen Fehler auszugeben, wenn etwas überschrieben wird, ohne dass es als virtuell markiert ist (und der überschreibende als überschreiben oder endgültig markiert ist).

Ich denke, dass TypeScript ohne override seine Benutzer in Bezug auf sein [wahrgenommenes] Versprechen von Kompilierungssicherheit und Typsicherheit enttäuscht.
Das Argument "Verwenden Sie einen IDE-Befehl, um die Methode zu überschreiben" bricht zusammen, wenn sich der Code weiterentwickelt (wie andere Leute darauf hingewiesen haben).
Es sieht auch so aus, als hätten alle wichtigen vergleichbaren Sprachen in irgendeiner Form override hinzugefügt.
Um es nicht zu einer bahnbrechenden Änderung zu machen, könnte es eine Tslint-Regel sein (ähnlich wie in Java).
Übrigens, warum nicht Breaking Changes zwischen den Hauptversionen zulassen, zB 2.x -> 3.x. Wir wollen nicht stecken bleiben, oder?
Wenn sich herausstellt, dass mit bestimmten Details von override etwas nicht stimmt und es in 3.x einige Breaking-Change-Optimierungen erfordert, sei es so. Ich denke, die meisten Leute werden den Kompromiss zwischen Evolutionsgeschwindigkeit und Kompatibilität verstehen und schätzen. Wirklich ohne override kann ich TS nicht als etwas Praktischeres als Java oder C# empfehlen ...

Wenn die Leute streng sein wollen, sollte es eine Möglichkeit geben, obligatorisch override zu schreiben (könnte eine optionale Tslint-Regel sein). Der Wert der obligatorischen override ist jedoch viel geringer, da die Wahrscheinlichkeit, versehentlich eine Methode aus der Superklasse zu überschreiben, viel geringer zu sein scheint als das versehentliche Nicht-Überschreiben von .

Diese Ausgabe, die seit 2015 offen ist, ist wirklich ein Eimer kaltes Wasser für meine TypeScript-Enthusiasmus und Evangelisation ...

Dies behandelt keine überschreibenden Methoden von Großeltern:

/* Put this in a helper library somewhere */ function override(container, key, other1) { var baseType = Object.getPrototypeOf(container); if(typeof baseType[key] !== 'function') { throw new Error('Method ' + key + ' of ' + container.constructor.name + ' does not override any base class method'); } }

Es ist auch traurig und enttäuschend, dass No one assigned auf GH hier zu diesem Thema. Vielleicht Zeit für einen TypeScript-Fork? ;) CompileTimeSafeScript...

Dieser ganze Thread sieht aus wie ein großes „Paralyse durch Analyse“-Antimuster. Warum nicht einfach den "80% Fallwert mit 20% Arbeit" machen und in der Praxis ausprobieren und ggf. in 3.x optimieren?

Die einfachen, häufigen und sehr wertvollen Anwendungsfälle werden von einigen Eckfällen „als Geiseln“ gehalten, um die sich die meisten Menschen nie wirklich kümmern müssen.

Es ist also möglich, dies zur Kompilierzeit über Decorators zu überprüfen, wenn auch nicht ohne ein wenig Hilfe:

export const override = <P extends Function>() => <K extends keyof P["prototype"]>(
  target: Object,
  methodName: K,
  descriptor: TypedPropertyDescriptor<P["prototype"][K]>
) => {
  // this is a no-op. The checking is all performed at compile-time, so runtime checks are not needed.
}

class Bar {
  biz (): boolean {
    return true;
  }

  qux (): string {
    return "hi";
  }
}

class Foo extends Bar {
  // this is fine
  @override<typeof Bar>()
  biz (): boolean {
    return false;
  }

  // error: type '() => number' is not assignable to type '() => boolean'
  @override<typeof Bar>()
  biz (): number {
    return 5;
  }

  // error: argument of type '"baz"' is not assignable to parameter of type '"biz" | "qux"'
  @override<typeof Bar>()
  baz (): boolean {
    return false;
  }
}

Ich bin mir nicht sicher, ob es möglich ist, einen Verweis auf den Supertyp zu erhalten, ohne ihn explizit zu übergeben. Dies verursacht geringfügige Laufzeitkosten, aber die Überprüfung ist vollständig Kompilierzeit.

@alangpierce

Mein Eindruck ist, dass eine TSLint-Regel hier bei weitem der reibungsloseste Weg nach vorne wäre.

[...]

Dann könnte es vielleicht als TSLint-Kernregel hinzugefügt werden, und vielleicht würde das genug Klarheit und Motivation bieten, um den ästhetischen Vorteil eines Überschreibungsschlüsselworts in der Kernsprache zu rechtfertigen.

100% einverstanden. Fangen wir schon an!

Hallo - etwas später als geplant, aber hier ist unsere Tslint-Regel, die mit einem Dekorateur implementiert wurde.

https://github.com/bet365/override-linting-rule

Wir verwenden dies seit einigen Jahren in unserer ~1mloc-TypeScript-Codebasis und haben es kürzlich aktualisiert, um den Sprachdienst zu verwenden, wodurch es viel schneller ausgeführt und für Open Source praktikabler wird (die vorherige Iteration erforderte einen vollständigen Durchlauf des Projekts um Informationen zum Kulturerbe zu sammeln).

Für unseren Anwendungsfall war es von unschätzbarem Wert - obwohl wir immer noch der Meinung sind, dass die Verantwortung letztendlich beim Compiler liegen sollte und die Linting-Regel als solche als Notlösung angesehen werden sollte.

Danke

Ich stimme zu. Ich glaube auch, dass eine Lösung mit dem TS-Compiler viel besser wäre als Decorators und Lint-Regeln.

Wie ist der Status dieser Funktion? Wurde es schon als Compiler-Funktion überarbeitet?

Es ist also möglich, dies zur Kompilierzeit über Decorators zu überprüfen, wenn auch nicht ohne ein wenig Hilfe:

Einige Verbesserungen:

function override< Sup >( sup : { prototype : Sup } ) {
    return <
        Field extends keyof Sup ,
        Proto extends { [ key in Field ] : Sup[ Field ] } ,
    >(
        proto : Proto ,
        field : Field ,
        descr : TypedPropertyDescriptor< Sup[ Field ] > ,
    )=> {}
}

class Foo {

    bar( a : number ) {
        return a 
    }

    bar2( a : number , b : number ) {
        return a 
    }

}

class Foo2 {

    @override( Foo )
    bar( a : number ) {
        return 1
    }

    @override( Foo )
    bar2( a : number , b : number ) {
        return 1 
    }

    xxx() { return '777' }

}

class Foo3 extends Foo2 {

    @override( Foo ) // OK
    bar( a : number ) { return 5 }

    @override( Foo ) // Error: less args than should
    bar2( a : number ) { return 5 }

    @override( Foo ) // Error: accidental override Foo2
    xxx() { return '666' }

    @override( Foo ) // Error: override of absent method
    yyy() { return 0 }

}

Spielplatz

Ok, Überschreibungslösung gefunden, die Kompilierungsfehler verhindert.
Beispiel mit Node-Readable-Stream:

// Interface so you will keep typings for all Readable methods/properties that are not overriden:
// Fileds that are `Omit`-ed should be overriden (with any signature you want, it do not have to be compatible with parent class)
interface ReadableObjStream<T> extends Omit<stream.Readable, 'push' | 'read'> {}

// Use extends (TYPE as any) to avoid compilation errors and override `Omit`-ted methods
class ReadableObjStream<T> extends (stream.Readable as any) {
    constructor()  {
        super({objectMode: true}); // force object mode. You can merge it with original options
    }
    // Override `Omit`-ed methods with YOUR CUSTOM SIGNATURE (can be non-comatible with parent):
    push(myOwnNonCompatibleSignature: T): string  { /* implementation*/ };
    read(options_nonCompatibleSignature: {opts: keyof T} ): string  { /* implementation*/ }
}

let typedReadable = new ReadableObjMode<{myData: string}>();
typedReadable.push({something: 'else'}); // will throw compilation error as expected
typedReadable.pipe(...) // non overloaded methods typings supported as expected

Der einzige Nachteil dieser Lösung ist das Fehlen von Eingaben beim Aufruf super.parentMethod (aber dank des interface ReadableObjStream haben Sie alle Eingaben, wenn Sie eine Instanz von ReadableObjStream verwenden.

@nin-jin @bioball Danke für die Beiträge mit der Kompilierzeitüberprüfung des Dekorateurs.

Leider scheint es mit protected Mitgliedern nicht zu funktionieren, nur mit public .

(Beispiel für einen Fehler in meinem Fork von Nin-Jin's Playground

Der Override-Spezifizierer war ein Killer-Feature in c++ 11.
Es hilft und schützt Refactoring-Code.
Ich möchte dies auf jeden Fall ohne Hürden in der TS-Basisunterstützung haben (STIMMEN!)

Wir haben definitiv wilde Unterstützung für diese Funktion gezeigt, aber es scheint, dass sie immer noch nicht planen, diese Funktion in absehbarer Zeit hinzuzufügen.

Eine andere Idee:

class Obj { 

    static override<
        This extends typeof Obj,
        Over extends keyof InstanceType<This> = never,
    >(this: This, ...overs: Over[]) { 
        return this as This & (
            new(...a:any[])=> InstanceType<This> & Protect< Omit<InstanceType<This> , Over > >
        )
    }

}

class Foo extends Obj {

    bar(a: number) {
        return 0
    }

    bar2(a: number) {
        return 0
    }

    foo = 1

}

class Foo2 extends Foo.override('bar') {

    foo = 2

    bar( a : number ) {
        return 1
    }

    // Error: Class 'Foo & Protect<Pick<Foo, "bar2" | "foo">>'
    // defines instance member property 'bar2',
    // but extended class 'Foo2' defines it as instance member function.
    bar2( a : number ) {
        return 1
    }

    bar3( a : number ) {
        return 1
    }

}

declare const Protected: unique symbol

type Protect<Obj> = {
    [Field in keyof Obj]:
    Object extends () => any
    ? Obj[Field] & { [Protected]: true }
    : Obj[Field]
}

Spielplatz Link

Fügen Sie hier meine zwei Cent hinzu.
Wir haben eine typische Winkelkomponente, die Formulare verarbeitet und Wertänderungen und dergleichen abbestellen muss. Wir wollten den Code nicht überall wiederholen (irgendwie aktueller Stand), also habe ich eine "TypicalFormBaseComponent" erstellt, die (unter anderem) die eckigen OnInit- und OnDestroy-Methoden implementiert. Das Problem ist, dass Sie jetzt, wenn Sie es tatsächlich verwenden und Ihre eigene OnDestroy-Methode hinzufügen (was ziemlich üblich ist), die ursprüngliche Methode ausblenden und das System beschädigen. Der Aufruf von super.OnInit behebt das Problem, aber es gibt derzeit keinen Mechanismus, mit dem ich die Kinder dazu zwingen könnte.

Wenn Sie es mit dem Konstruktor machen, zwingt es Sie, super () aufzurufen ... Ich habe nach etwas Ähnlichem gesucht, diesen Thread gefunden und ehrlich gesagt bin ich ein bisschen verblüfft.
Das Implementieren von "new" und "override" in den ts könnte eine bahnbrechende Änderung sein, aber das Beheben wäre in praktisch jeder Codebasis unglaublich einfach (einfach "override" überall dort hinzufügen, wo es schreit). Es kann auch nur eine Warnung sein .

Wie auch immer, gibt es eine andere Möglichkeit für mich, den Superruf in den Kindern zu erzwingen? TSLint-Regel, die das Verstecken verhindert, oder so ähnlich?

PS: Ich stimme der „Kein-Kommentar“-Richtlinie nicht zu. Es hindert Leute daran, hier Beispiele zu werfen. Vielleicht werden Sie 'override' nicht implementieren, aber Sie werden etwas anderes implementieren, das sich um ihre Probleme kümmert ... das heißt, wenn Sie sie tatsächlich lesen können.

@GonziHere In anderen Sprachen wie C# oder Java bedeutet das Überschreiben nicht, dass die Supermethode aufgerufen werden muss - dies ist tatsächlich oft nicht der Fall. Konstruktoren sind spezielle und keine normalen "virtuellen Methoden".

Das Schlüsselwort override würde verwendet, um anzugeben, dass die Methode bereits mit einer kompatiblen Signatur in der Basisklasse definiert sein muss, und der Compiler könnte dies bestätigen.

Ja, aber die Notwendigkeit des Überschreibens zwingt Sie dazu, zu bemerken, dass die Methode existiert.

@GonziHere Sieht so aus, als ob Sie wirklich abstrakte Basisklassen mit abstrakten Funktionen erstellen müssen. Vielleicht können Sie private _onInit - und _onDestroy -Methoden erstellen, auf die Sie sich mehr verlassen können, und dann geschützte abstrakte onInit - und onDestroy -Funktionen (oder reguläre Funktionen, falls vorhanden) erstellen nicht erforderlich). Die privaten Funktionen rufen die anderen auf und werden dann wie gewohnt abgeschlossen.

@GonziHere @rjamesnw Am sichersten ist es tatsächlich, irgendwie zu erzwingen, dass onInit und onDestroy _final_ sind, und leere geschützte abstrakte Template-Methoden zu definieren, die die finalen Methoden aufrufen. Dies garantiert, dass niemand die Methoden versehentlich außer Kraft setzen kann, und der Versuch, dies zu tun (vorausgesetzt, es gäbe eine Möglichkeit, endgültige Methoden zu erzwingen), würde den Benutzern sofort den richtigen Weg weisen, das zu implementieren, was sie wollen.

@shicks , @rjamesnw , ich bin in der gleichen Situation wie @GonziHere. Ich möchte nicht, dass die Basisklasse abstrakte Methoden hat, da es eine Funktionalität gibt, die ich für jeden dieser Lebenszyklus-Hooks ausführen möchte. Das Problem ist, dass ich nicht möchte, dass diese Basisfunktionalität versehentlich ersetzt wird, wenn jemand ngOnInit oder ngOnDestroy zur untergeordneten Klasse hinzufügt, ohne super() aufzurufen. Das Erfordernis override würde den Entwickler darauf aufmerksam machen, dass diese Methoden in der Basisklasse vorhanden sind und dass er wählen sollte, ob er super() aufrufen muss;

Aus meiner Erfahrung beim Schreiben von Java ist @Override so verbreitet, dass es zu Rauschen wird. In vielen Fällen ist die überschriebene Methode abstrakt oder leer (wobei super() _nicht_ aufgerufen werden sollte), wodurch das Vorhandensein von override kein besonders deutliches Signal dafür ist, dass super() benötigt wird.

Aus meiner Erfahrung beim Schreiben von Java ist @Override so verbreitet, dass es zu Rauschen wird. In vielen Fällen ist die überschriebene Methode abstrakt oder leer (wobei super() _nicht_ aufgerufen werden sollte), wodurch das Vorhandensein von override kein besonders deutliches Signal dafür ist, dass super() benötigt wird.

Das hat nichts mit dem behandelten Thema zu tun. Sie könnten genauso gut sagen, dass Sie Komposition über Vererbung verwenden, um das Problem zu vermeiden.

Für mich ist der nützlichste Teil von override , dass beim Refactoring ein Fehler angezeigt wird.
Im Moment ist es allzu einfach, einige Methoden in einer Basisklasse umzubenennen und zu vergessen, diejenigen umzubenennen, die sie überschreiben.
Sich daran zu erinnern, super zu rufen, ist nicht so wichtig. Es ist sogar richtig, es in einigen Fällen nicht zu nennen.

Ich denke, dass es ein Missverständnis darüber gibt, warum die _override_ wichtig wäre. Nicht, um jemanden zu zwingen, die _super()_-Methode aufzurufen, sondern um ihn darauf aufmerksam zu machen, dass es eine gibt, und ihm die Möglichkeit zu geben, gewissenhaft zu entscheiden, ob er sein Verhalten unterdrücken oder erweitern möchte.

Das Muster, das ich verwende, um die Korrektheit sicherzustellen, besteht darin, Parameters und ReturnType zu verwenden, während ich mich sehr explizit auf die Basisklasse beziehe, etwa so:

class Base {
    public methodName(arg1: string, arg2: number): boolean {
        return false; // base behaviour, may be stub.
    }
}
class Derived extends Base {
    public methodName(...args: Parameters<Base["methodName"]>): ReturnType<Base["methodName"]> {
        const [meaningful, variableNames] = args;
        return true; // implemented behaviour here.
    }
}

Diese Art von Muster ist die Grundlage für meinen Vorschlag, ein inherit -Schlüsselwort hinzuzufügen. . Dadurch wird sichergestellt, dass alle Aktualisierungen der Basisklasse automatisch weitergegeben werden, und wenn sie in der abgeleiteten Klasse ungültig sind, wird ein Fehler ausgegeben, oder bei Namensänderungen der Basisklasse wird ebenfalls ein Fehler ausgegeben, auch mit der inherit -Idee Sie sind nicht darauf beschränkt, nur eine identische Signatur zur Basisklasse anzubieten, sondern können sie intuitiv erweitern.

Ich denke auch, dass es ein Missverständnis darüber gibt, warum override wichtig wäre, aber ich verstehe all diese Probleme mit "ob super() benötigt wird" nicht wirklich.

Um zu wiederholen, was @lorenzodallavecchia und der ursprüngliche Autor des Problems gesagt haben, ist das Markieren einer Funktion als override ein Mechanismus, um Fehler zu vermeiden, die passieren, wenn die Oberklasse umgestaltet wird, insbesondere wenn der Name und/oder die Signatur von die überschriebene Funktion ändert sich oder die Funktion wird entfernt.

Denken Sie daran, dass die Person, die die Signatur der überschriebenen Funktion ändert, möglicherweise nicht weiß, dass Überschreibungen vorhanden sind. Wenn (z. B. in C++) die Überschreibungen nicht explizit als Überschreibungen gekennzeichnet sind, führt das Ändern des Namens/der Signatur der überschriebenen Funktion nicht zu einem Kompilierungsfehler, sondern bewirkt lediglich, dass diese Überschreibungen keine Überschreibungen mehr sind. (und ... wahrscheinlich nicht mehr nützlich sein, nicht mehr von Code aufgerufen werden, der sie früher aufgerufen hat, und eine Reihe neuer Fehler einführen, da Dinge, die die Überschreibungen aufrufen sollen, jetzt die Basisimplementierung aufrufen)

Das Schlüsselwort override, wie es in C++ implementiert ist, erspart der Person, die die Basisimplementierung ändert, diese Probleme, da Kompilierungsfehler unmittelbar nach ihrer Umgestaltung darauf hinweisen, dass eine Reihe von Überschreibungen vorhanden sind (die eine jetzt nicht vorhandene Basisimplementierung überschreiben) und Hinweise geben ihnen die Tatsache, dass sie wahrscheinlich auch die Überschreibungen umgestalten müssen.

Der Modifikator override hat auch sekundäre Vorteile für die Benutzerfreundlichkeit der IDE (auch vom ursprünglichen Autor erwähnt), nämlich dass die IDE Ihnen bei der Eingabe des Wortes override hilfreich eine Reihe von Möglichkeiten zeigen kann kann überschrieben werden.

Zusammen mit dem Schlüsselwort override wäre es gut, auch ein Flag einzuführen, das, wenn es aktiviert ist, erfordert, dass alle Methoden, die eine andere überschreiben, das Schlüsselwort override verwenden , um Fälle zu vermeiden, in denen Sie ein erstellen Methode in einer Unterklasse und in Zukunft erstellt die Basisklasse (die sich in einer Bibliothek eines Drittanbieters befinden könnte) eine Methode mit demselben Namen wie die Methode, die Sie in der Unterklasse erstellt haben (was jetzt zu Fehlern führen kann, da die Unterklasse diese Methode überschrieben hat ).

In diesem Fall erhalten Sie einen Kompilierungsfehler, der besagt, dass Sie das Schlüsselwort override in dieser Methode hinzufügen müssen (auch wenn Sie die Methode möglicherweise nicht wirklich überschreiben, ändern Sie einfach ihren Namen , um die neu erstellte Methode in nicht zu überschreiben die Basisklasse), wäre viel besser und vermeide mögliche Laufzeitfehler.

Aus meiner Erfahrung beim Schreiben von Java ist @Override so verbreitet, dass es zu Rauschen wird.

Nach vielen Java-Jahren widerspreche ich obiger Aussage entschieden. Override ist eine Art der Kommunikation, wie geprüfte Ausnahmen. Es geht nicht um Likes oder Dislikes, sondern um Qualität und Erwartungen.

Und deshalb ein großes +1 an @lucasbasquerotto , aber aus einem anderen Blickwinkel: Wenn ich eine Methode in einer Unterklasse einführe, möchte ich eine klare Semantik des Überschreibens oder Nichtüberschreibens haben. Wenn ich beispielsweise eine Methode überschreiben möchte, möchte ich eine Möglichkeit haben, dies explizit mitzuteilen. Wenn etwas mit meiner Implementierung nicht stimmt, zum Beispiel ein Tippfehler oder falsches Kopieren und Einfügen, dann möchte ich Feedback dazu bekommen. Oder anders gesagt: Wenn ich kein Überschreiben erwarte, möchte ich auch eine Rückmeldung, wenn gelegentlich ein Überschreiben auftritt.

Feedback von einem anderen Java-Entwickler ... @override ist die einzige Funktion, die ich wirklich in Typescript vermisse.

Ich habe ungefähr 15 Java-Erfahrung und 1-2 Jahre oder Typescript, also bin ich mit beiden ziemlich vertraut.

Das Zen von @override ist, dass Sie eine Methode von einer Schnittstelle entfernen können und die Kompilierung bricht.

Es ist wie die Umkehrung der engen Bindung für neue Methoden. Es ermöglicht Ihnen, alte zu entfernen.

Wenn Sie eine Schnittstelle implementieren und eine Methode HINZUFÜGEN, schlägt die Kompilierung fehl, aber es gibt keine Umkehrung davon.

Wenn Sie eine Methode entfernen, erhalten Sie nur toten Code.

(obwohl das vielleicht durch einen Linter behoben werden kann)

Um es klar zu sagen, als ich sagte, dass @Override Rauschen sei, bezog ich mich speziell auf den Kommentar, dass es ein Signal sei, dass Sie super anrufen müssen. Die Überprüfung, die es bietet, ist wertvoll, aber für jede gegebene überschriebene Methode ist es eine vollständige Frage, ob es erforderlich oder sinnlos ist, super aufzurufen. Da ein so großer Teil überschriebener Methoden _nicht_ sollte_, ist es für diesen Zweck unwirksam.

Mein einziger Punkt ist, dass, wenn Sie sicherstellen möchten, dass Unterklassen Ihre überschreibbaren Methoden zurückrufen, der einzige effektive Weg, dies zu tun, darin besteht, die Methode final (siehe unter anderem #33446) zu machen und sie in a aufrufen zu lassen _anders benannte_ leere Vorlagenmethode, die _ohne_ den super -Aufruf sicher überschrieben werden kann. Es gibt einfach keinen anderen vernünftigen Weg, um diese Invariante zu erhalten. Ich bin voll und ganz für override , aber als jemand, der von Benutzern verbrannt wurde, die meine APIs fälschlicherweise in Unterklassen umwandelten (und ich bin fest entschlossen, sie nicht zu beschädigen), glaube ich, dass es sich lohnt, die Aufmerksamkeit auf final zu lenken override , wie oben vorgeschlagen wurde.

Ich bin ein wenig verwirrt darüber, warum die Leute immer wieder eines von final oder override vorschlagen. Sicherlich wollen wir beides , da sie unterschiedliche Probleme lösen.

Überschreiben
Verhindert, dass Personen, die eine Klasse erweitern, versehentlich eine Funktion mit ihrer eigenen überschreiben und dabei Dinge beschädigen, ohne es zu wissen. Ein Override-Schlüsselwort lässt den Entwickler wissen, dass er tatsächlich eine vorhandene Funktion überschreibt, und dann kann er entweder seine Funktion umbenennen oder die Überschreibung verwenden (und dann kann er herausfinden, ob er super aufrufen muss).

Finale
Verhindern Sie, dass Personen, die eine Klasse erweitern, eine Funktion vollständig überschreiben, damit der ursprüngliche Ersteller der Klasse die vollständige Kontrolle garantieren kann.

@sam-s4s Wir wollen das Definieren auch :-)

Problembeispiel..

Initial

class Base {}
class Entity extends Base {
    id() {
        return 'BUG-123' // busisess entity id
    }
}

Basisklasse umgestaltet

class Base {
    id() {
        return '84256635572' // storage object id
    }
}
class Entity extends Base {
    id() {
        return '12' // busisess entity id
    }
}

Wir haben hier eine versehentliche Überlastung.

Fall mit Schlüsselwort define

class Base {
    define id() {
        return '84256635572' // storage object id
    }
}
class Entity extends Base {
    define id() {
        return '12' // busisess entity id
    }
}

Sollte ein Fehler sein: Versehentliche Neudefinition.

Problemumgehung mit Dekorateuren

@nin-jin ist define nicht dasselbe wie _not_ mit override ?

@sam-s4s Wir wollen das Definieren auch :-)

Oh, ich habe hier noch niemanden gesehen, der ein Schlüsselwort define erwähnt hat - aber von dem, was Sie beschreiben, nehme ich an, Sie meinen wie das Wort virtual in C#?

(Außerdem fand ich Ihr Beispiel ein wenig verwirrend, da Ihre Klassen Base und Entity nicht miteinander verwandt sind. Meinten Sie, dass Entity Base erweitern?)

Ich denke es gibt 2 Szenarien...
a) Sie schreiben Ihre Basisklasse unter der Annahme, dass alles ohne final überschrieben werden kann.
b) Sie schreiben Ihre Basisklasse unter der Annahme, dass alles ohne virtual nicht überschrieben werden kann.

@sam-s4s Entity erweitert natürlich Base. :-) Ich habe meine Nachricht korrigiert. virtual geht es um etwas anderes.
@lorenzodallavecchia , das override nicht verwendet, ist define | override für den Compiler. Die Verwendung define ist nur define und der Compiler kann dies streng überprüfen.

@nin-jin In Ihrem Modell bedeutet dies, dass die Nichtverwendung des Schlüsselworts override immer noch legal ist, oder? Zum Beispiel:

class Base {
  myMethod () { ... }
}

class Overridden extends Base {
  // this would not fail because this is interpreted as define | override.
  myMethod () { ... }
}

Im Idealfall erzeugt das obige Beispiel einen Fehler, es sei denn, Sie verwenden das Schlüsselwort override . Allerdings stelle ich mir vor, dass es ein Compiler-Flag geben würde, um die Überschreibungsprüfung abzulehnen, wobei das Ablehnen dasselbe ist wie override | define für alle Methoden

@bioball ja, es ist für die Kompatibilität mit vielen vorhandenen Codes erforderlich.

@sam-s4s Entity erweitert natürlich Base. :-) Ich habe meine Nachricht korrigiert. virtual geht es um etwas anderes.

Ich bin mir immer noch nicht sicher, was Sie mit definieren meinen ... dies ist die Definition von virtual in C#:

The virtual keyword is used to modify a method, property, indexer, or event declaration and allow for it to be overridden in a derived class.

Vielleicht meinen Sie das Gegenteil, wo Sie, anstatt die Funktion als virtual markieren zu müssen, um sie zu überschreiben, die Funktion als define markieren können, um dann zu bedeuten, dass Sie override verwenden müssen

(warum define ? Ich habe dieses Schlüsselwort in keiner anderen Sprache gesehen)

Ich habe diesen Thread in ein paar Minuten überflogen, aber ich habe niemanden gesehen, der vorgeschlagen hat, override zu verwenden, um eine doppelte Angabe von Parametern und Rückgabewerten zu vermeiden. Zum Beispiel:

class Animal {
    move(meters:number):void {
    }
}

class Snake extends Animal {
    override move(meters) {
    }
}

Im obigen Beispiel hätte move denselben erforderlichen Rückgabetyp ( void ) und meters hätte ebenfalls denselben Typ, aber die Angabe wäre nicht erforderlich. Das große Unternehmen, für das ich arbeite, versucht, unser gesamtes Javascript auf Typoskript zu migrieren, weg von der Closure-Compiler-Eingabe. In Closure können wir jedoch einfach @override verwenden und alle Typen werden von der Oberklasse abgeleitet. Dies ist sehr nützlich, um die Möglichkeit einer Nichtübereinstimmung zu verringern und eine Duplizierung zu verringern. Man könnte sich sogar vorstellen, dies so zu implementieren, dass eine Erweiterung der Methode mit zusätzlichen Parametern immer noch möglich ist, auch ohne Typen für die Parameter anzugeben, die in der Oberklasse angegeben sind. Zum Beispiel:

class Animal {
    move(meters:number):number {
    }
}

class Snake extends Animal {
    override move(meters, time:number) {
    }
}

Die Motivation für mich, hierher zu kommen und einen Kommentar zu schreiben, ist, dass unser Unternehmen jetzt verlangt, dass wir alle Typen angeben, sogar für überschriebene Methoden, weil Typoskript diese Funktionalität nicht hat (und wir uns in einer langen Migration befinden). Es ist ziemlich nervig.

FWIW ist zwar einfacher zu schreiben, aber das Weglassen von Parametertypen (und anderen Fällen von dateiübergreifender Typinferenz) behindert die Lesbarkeit, da jemand, der mit dem Code nicht vertraut ist, herumgraben muss, um herauszufinden, wo die Oberklasse definiert ist, um zu wissen, welcher Typ meters ist

@shicks Das könnte man buchstäblich über jede importierte Variable oder Klasse sagen. Das Duplizieren aller Informationen, die Sie möglicherweise benötigen, in einer einzigen Datei widerspricht dem Zweck der Abstraktion und Modularität. Das Duplizieren von Typen zu erzwingen verstößt gegen das DRY-Prinzip.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen