Warum ist das Schlüsselwort 'this' erforderlich, um eine Erweiterungsmethode innerhalb der erweiterten Klasse aufzurufen?

79

Ich habe eine Erweiterungsmethode für eine ASP.NET MVC ViewPage erstellt, z.

public static class ViewExtensions
{
    public static string Method<T>(this ViewPage<T> page) where T : class
    {
        return "something";
    }
}

Beim Aufrufen dieser Methode aus einer Ansicht (abgeleitet von ViewPage) wird die Fehlermeldung " CS0103: Der Name 'Methode' ist im aktuellen Kontext nicht vorhanden " angezeigt, es sei denn, ich verwende das thisSchlüsselwort, um sie aufzurufen:

<%: Method() %> <!-- gives error CS0103 -->
<%: this.Method() %> <!-- works -->

Warum ist das thisSchlüsselwort erforderlich? Oder funktioniert es ohne, aber mir fehlt etwas?

(Ich denke, es muss ein Duplikat dieser Frage geben, aber ich konnte keines finden)

Update :

Wie Ben Robinson sagt , ist die Syntax zum Aufrufen von Erweiterungsmethoden nur Compilerzucker. Warum kann der Compiler dann nicht automatisch nach Erweiterungsmethoden der Basistypen des aktuellen Typs suchen, ohne das Schlüsselwort this zu benötigen?

M4N
quelle
@ M4N - Dies scheint das mögliche Duplikat zu sein, nach dem Sie gesucht haben
djdd87
@GenericTypeTea: Ja, das ist eine ähnliche Frage. Ich frage jedoch nicht, wie die Erweiterungsmethode aufgerufen werden soll (ich weiß, dass ich das thisSchlüsselwort hinzufügen muss ), sondern ich frage, warum das thisSchlüsselwort erforderlich ist. Ich habe meine Frage aktualisiert.
M4N
@ M4N - Guter Punkt, sie sagen, dass es getan werden muss, aber die Antworten geben nicht genug Erklärung, warum.
djdd87
Bei Instanzmethoden wird 'this' implizit an die Methode übergeben. Wenn Sie Method () anstelle von this.Method () oder Method (this) aufrufen, teilen Sie dem Compiler nicht mit, was an die Methode übergeben werden soll.
Alex Humphrey
@Alex. Technisch gesehen stelle ich mir vor, dass der Compiler im Kontext des Aufrufs von Method () auf "dies" schließen könnte, aber ich denke, dass dies eine schlechte Wahl für das Design gewesen wäre und Code weniger lesbar gemacht hätte.
Ben Robinson

Antworten:

55

Ein paar Punkte:

Zunächst einmal ist die vorgeschlagene Funktion (implizites "this" bei einem Aufruf einer Erweiterungsmethode) nicht erforderlich . Erweiterungsmethoden waren erforderlich, damit das Verständnis von LINQ-Abfragen wie gewünscht funktioniert. Der Empfänger wird immer in der Abfrage angegeben, daher ist es nicht erforderlich, dies implizit zu unterstützen, damit LINQ funktioniert.

Zweitens arbeitet die Funktion gegen den allgemeineren Design von Erweiterungsmethoden: nämlich, dass die Erweiterungsmethoden ermöglichen es Ihnen , eine Art zu erweitern , dass Sie sich nicht erweitern können , sei es, weil es sich um eine Schnittstelle ist , und Sie wissen nicht , die Implementierung, oder weil Sie tun kennen die Implementierung, haben aber nicht den Quellcode.

Wenn Sie in dem Szenario, wo Sie eine Erweiterungsmethode für einen Typ verwenden in dieser Art dann Sie haben Zugriff auf den Quellcode. Warum verwenden Sie dann überhaupt eine Erweiterungsmethode? Sie können selbst eine Instanzmethode schreiben, wenn Sie Zugriff auf den Quellcode des erweiterten Typs haben, und dann müssen Sie überhaupt keine Erweiterungsmethode verwenden! Ihre Implementierung kann dann den Zugriff auf den privaten Status des Objekts nutzen, den Erweiterungsmethoden nicht können.

Wenn Sie die Verwendung von Erweiterungsmethoden innerhalb eines Typs, auf den Sie Zugriff haben, vereinfachen möchten, wird die Verwendung von Erweiterungsmethoden gegenüber Instanzmethoden empfohlen. Erweiterungsmethoden sind großartig, aber es ist normalerweise besser, eine Instanzmethode zu verwenden, wenn Sie eine haben.

Angesichts dieser beiden Punkte muss der Sprachdesigner nicht mehr erklären, warum die Funktion nicht vorhanden ist. Es liegt nun an Ihnen zu erklären, warum es sollte . Features sind mit enormen Kosten verbunden. Diese Funktion ist nicht erforderlich und widerspricht den angegebenen Entwurfszielen von Erweiterungsmethoden. Warum sollten wir die Kosten für die Implementierung übernehmen? Erklären Sie, welches überzeugende, wichtige Szenario durch diese Funktion ermöglicht wird, und wir werden in Betracht ziehen, es in Zukunft zu implementieren. Ich sehe kein zwingendes, wichtiges Szenario, das dies rechtfertigt, aber vielleicht gibt es eines, das ich verpasst habe.

Eric Lippert
quelle
11
Danke für diese Antwort! Zwei Punkte: 1: Ich habe nicht darum gebeten, dass diese Funktion implementiert wird, sondern nur um eine Erklärung, warum sie nicht vorhanden ist / warum sie thiserforderlich ist. 2: Warum rufe ich eine Erweiterungsmethode vom erweiterten Typ auf? In dem angegebenen Beispiel füge ich der Basisklasse (ViewPage) einige praktische Methoden hinzu und rufe sie aus abgeleiteten Klassen auf. Dadurch entfällt die Notwendigkeit, eine gemeinsame Basisklasse für alle meine Ansichten zu erstellen. Übrigens: Ich stimme zu, dass die Verwendung einer Erweiterungsmethode innerhalb des erweiterten Typs umständlich ist, aber siehe auch das Beispiel mit MVC ViewPage.
M4N
3
@ M4N: Genau das ist auch meine Situation: Ich möchte die UserControl-Methode erweitern und diese Erweiterung allen meinen UserControls ohne das explizite 'this' zur Verfügung stellen. Ich weiß, dass die Entwicklung von Compilern keine Demokratie ist, aber ich betrachte dies als meine Stimme für ein implizites 'dies' :-)
Duncan Bayne
8
Hallo Eric, zugegeben, dies ist kein primäres Szenario. Das allgemeine Muster scheint jedoch 1 zu sein. Sie haben keinen Quellbearbeitungszugriff auf eine Basisklasse. 2. Sie möchten dem Basistyp einige (geschützte) Methoden hinzufügen, die Sie in den Körpern der abgeleiteten Klassen aufrufen möchten. Gibt es einen anderen Mechanismus, um dies zu erreichen? this.MethodName ist nicht so schwer zu tippen, aber es wird nach einer Weile nervig ...
Gishu
5
Ich denke, das Szenario des Hinzufügens einer Erweiterungsmethode zu einer Basisklasse (für die Sie keine Quelle haben) ist gültig. In diesem Fall wäre es schön gewesen, die Methode ohne "this" aufrufen zu können. Übrigens kann dies in der nächsten c # -Version mit "statischen Importen" gelöst werden.
Lysergsäure
4
Ein gültiger Grund für die Verwendung von Erweiterungsmethoden aus der Klasse, die Sie erweitern, besteht darin, dass Sie mehrere Typen haben, die eine Schnittstelle implementieren. Sie haben beispielsweise zwei Klassen, die implementiert werden ICrossProductable, und Sie definieren eine Erweiterungsmethode auf dieser Schnittstelle, die aufgerufen wird GetProduct. Sehr einfaches Beispiel, aber ich finde das oft nützlich. Es ist jedoch nicht in allen Fällen der beste Ansatz.
Ian Newson
9

Ohne sie sieht der Compiler es nur als statische Methode in einer statischen Klasse, die Seite als ersten Parameter verwendet. dh

// without 'this'
string s = ViewExtensions.Method(page);

vs.

// with 'this'
string s = page.Method();
Ferruccio
quelle
1
Ich denke, das sollte ViewExtensions.Method (Seite) sein.
Dave Van den Eynde
6

Bei Instanzmethoden wird 'this' implizit transparent an jede Methode übergeben, sodass Sie auf alle von ihr bereitgestellten Mitglieder zugreifen können.

Erweiterungsmethoden sind statisch. Wenn Sie Method()statt this.Method()oder aufrufen Method(this), teilen Sie dem Compiler nicht mit, was an die Methode übergeben werden soll.

Sie könnten sagen: "Warum erkennt es nicht einfach, was das aufrufende Objekt ist, und übergeben es als Parameter?"

Die Antwort ist, dass Erweiterungsmethoden statisch sind und aus einem statischen Kontext aufgerufen werden können, in dem es kein "dies" gibt.

Ich denke, sie könnten das während der Kompilierung überprüfen, aber um ehrlich zu sein, ist es wahrscheinlich eine Menge Arbeit für extrem wenig Gewinn. Und um ehrlich zu sein, sehe ich wenig Nutzen darin, einige der expliziten Aufrufe von Erweiterungsmethoden wegzunehmen. Die Tatsache, dass sie beispielsweise mit Methoden verwechselt werden können, bedeutet, dass sie manchmal ziemlich unintuitiv sind (NullReferenceExceptions werden beispielsweise nicht ausgelöst). Ich denke manchmal, dass sie einen neuen Pipe-Forward-Operator für Erweiterungsmethoden hätten einführen sollen.

Alex Humphrey
quelle
Es scheint also, als hätten sie eine wirklich schlechte Designentscheidung getroffen, damit es dann aus einem statischen Kontext aufgerufen werden kann, da ich nicht verstehe, warum das jemals jemand tun würde.
AustinWBryan
3

Es ist wichtig zu beachten, dass es Unterschiede zwischen Erweiterungsmethoden und regulären Methoden gibt. Ich denke, Sie sind gerade auf einen von ihnen gestoßen.

Ich gebe Ihnen ein Beispiel für einen weiteren Unterschied: Es ist ziemlich einfach, eine Erweiterungsmethode für eine nullObjektreferenz aufzurufen . Glücklicherweise ist dies mit regulären Methoden viel schwieriger zu tun. (Aber es kann getan werden. IIRC, Jon Skeet demonstrierte, wie dies durch Manipulieren des CIL-Codes zu tun ist.)

static void ExtensionMethod(this object obj) { ... }

object nullObj = null;
nullObj.ExtensionMethod();  // will succeed without a NullReferenceException!

Trotzdem stimme ich zu, dass es ein wenig unlogisch erscheint, thisdie Erweiterungsmethode aufzurufen. Schließlich sollte sich eine Erweiterungsmethode idealerweise wie eine normale Methode "fühlen" und verhalten.

In Wirklichkeit ähneln Erweiterungsmethoden jedoch eher syntaktischem Zucker, der zusätzlich zur vorhandenen Sprache hinzugefügt wird, als einem frühen Kernmerkmal, das in jeder Hinsicht gut in die Sprache passt.

stakx - nicht mehr beitragen
quelle
In der Boo-Sprache können Sie die Erweiterungsmethode oder -eigenschaft direkt auf null, dh null.Do (), aufrufen.
Sergey Mirvoda
1
@Sergey: Klingt gefährlich ... :) Macht mich neugierig, woher Boo den (implizierten) Typ kennt null? Könnte fast jeder Referenztyp sein, woher weiß es, auf welchen Methoden es verfügbar ist null?
stakx - nicht mehr beitragen
1
Es kann einen guten Grund geben, eine Erweiterungsmethode mit einer Nullreferenz aufrufen zu können.
Dave Van den Eynde
@ DaveVandenEynde: IMHO war es ein Fehler für .net, kein Attribut für nicht virtuelle Methoden zu definieren, das erfordern würde, dass Compiler sie mit nicht virtuellen Aufrufen aufrufen. Einige unveränderliche Klassentypen wie Stringlogisch verhalten sich wie Werte und können einen sinnvollen Standardwert haben, wenn null (z. B. .net kann eine Nullreferenz vom statischen Typ konsistent stringals leere Zeichenfolge betrachten, anstatt dies nur sporadisch zu tun). Statische Methoden für Dinge wie verwenden zu müssen, if (!String.IsNullOrEmpty(stringVar))ist einfach abscheulich.
Supercat
1
@ Supercat Ja, schrecklich. Sie können auch string.IsNullOrEmpty () ausführen. Viel schöner.
Dave Van den Eynde
1

Weil die Erweiterungsmethode mit der ViewPage-Klasse nicht vorhanden ist. Sie müssen dem Compiler mitteilen, für was Sie die Erweiterungsmethode aufrufen. Denken Sie daran, this.Method()ist nur Compiler Zucker für ViewExtensions.Method(this). Auf die gleiche Weise können Sie eine Erweiterungsmethode nicht einfach in der Mitte einer Klasse mit dem Methodennamen aufrufen.

Ben Robinson
quelle
1
Das macht (irgendwie) Sinn. Aber wenn es sich um Compilerzucker handelt, warum kann der Compiler dann nicht ohne das thisSchlüsselwort nach Erweiterungsmethoden suchen?
M4N
Sieht für mich nach einem Versehen aus. Wie Sie sagten - der Compiler konnte feststellen, dass die Methode nicht vorhanden ist, und dann die Erweiterungen auf Verfügbarkeit prüfen.
TomTom
Ja, das könnte wohl funktionieren. Ich würde vermuten, dass es eine spezifische Entwurfsentscheidung ist, den Code lesbarer zu machen. Wenn Sie einfach ExtensionMethod () innerhalb einer Klasse aufrufen könnten, könnte es für jemanden, der den Code liest, etwas verwirrend sein, wenn diese Klasse diese Methode nicht enthält, aber mit this.ExtensionMethod () zumindest mit MyInstance.ExtentionMethod () übereinstimmt.
Ben Robinson
Ich denke, dass this.Method () eher syntaktischer Zucker für ViewExtensions.Method (this) ist.
Alex Humphrey
1

Ich arbeite an einer fließenden API und bin auf dasselbe Problem gestoßen. Obwohl ich Zugriff auf die Klasse habe, die ich erweitere, wollte ich, dass die Logik jeder der fließenden Methoden in ihren eigenen Dateien enthalten ist. Das Schlüsselwort "this" war sehr unintuitiv. Die Benutzer dachten immer wieder, dass die Methode fehlte. Ich habe meine Klasse zu einer Teilklasse gemacht, die die benötigten Methoden implementiert hat, anstatt Erweiterungsmethoden zu verwenden. Ich sah keine Erwähnung von Teiltönen in den Antworten. Wenn Sie diese Frage haben, sind Teiltöne möglicherweise die bessere Option.

Reyn
quelle