Ich habe ein Szenario, in dem ich die Methodengruppensyntax anstelle anonymer Methoden (oder Lambda-Syntax) zum Aufrufen einer Funktion verwenden möchte.
Die Funktion hat zwei Überladungen, eine, die eine nimmt Action
, die andere nimmt eine Func<string>
.
Ich kann die beiden Überladungen gerne mit anonymen Methoden (oder Lambda-Syntax) aufrufen, erhalte jedoch einen Compilerfehler beim mehrdeutigen Aufruf, wenn ich die Methodengruppensyntax verwende. Ich kann dies durch explizites Casting an Action
oder umgehen Func<string>
, denke aber nicht, dass dies notwendig sein sollte.
Kann jemand erklären, warum die expliziten Besetzungen erforderlich sein sollten.
Codebeispiel unten.
class Program
{
static void Main(string[] args)
{
ClassWithSimpleMethods classWithSimpleMethods = new ClassWithSimpleMethods();
ClassWithDelegateMethods classWithDelegateMethods = new ClassWithDelegateMethods();
// These both compile (lambda syntax)
classWithDelegateMethods.Method(() => classWithSimpleMethods.GetString());
classWithDelegateMethods.Method(() => classWithSimpleMethods.DoNothing());
// These also compile (method group with explicit cast)
classWithDelegateMethods.Method((Func<string>)classWithSimpleMethods.GetString);
classWithDelegateMethods.Method((Action)classWithSimpleMethods.DoNothing);
// These both error with "Ambiguous invocation" (method group)
classWithDelegateMethods.Method(classWithSimpleMethods.GetString);
classWithDelegateMethods.Method(classWithSimpleMethods.DoNothing);
}
}
class ClassWithDelegateMethods
{
public void Method(Func<string> func) { /* do something */ }
public void Method(Action action) { /* do something */ }
}
class ClassWithSimpleMethods
{
public string GetString() { return ""; }
public void DoNothing() { }
}
C # 7.3 Update
Wie pro 0xcde ‚s Kommentar unten am 20. März 2019 (9 Jahre nachdem ich diese Frage gestellt!), Ist dieser Code kompiliert wie C # 7.3 dank verbesserter Überlastung Kandidaten .
<LangVersion>7.3</LangVersion>
) oder höher verwenden, dank verbesserter Überlastungskandidaten .Antworten:
Lassen Sie mich zunächst nur sagen, dass Jons Antwort richtig ist. Dies ist einer der haarigsten Teile der Spezifikation, so gut für Jon, dass er mit dem Kopf voran in die Spezifikation eintaucht.
Zweitens, lassen Sie mich sagen, dass diese Zeile:
(Hervorhebung hinzugefügt) ist zutiefst irreführend und unglücklich. Ich werde mit Mads darüber sprechen, wie das Wort "kompatibel" hier entfernt wird.
Der Grund dafür ist irreführend und unglücklich, weil es so aussieht, als würde dies in Abschnitt 15.2, "Kompatibilität von Delegierten", beschrieben. In Abschnitt 15.2 wurde die Kompatibilitätsbeziehung zwischen Methoden und Delegatentypen beschrieben. Dies ist jedoch eine Frage der Konvertierbarkeit von Methodengruppen und Delegattypen , die unterschiedlich ist.
Nachdem wir das aus dem Weg geräumt haben, können wir Abschnitt 6.6 der Spezifikation durchgehen und sehen, was wir bekommen.
Um eine Überlastungsauflösung durchzuführen, müssen wir zuerst bestimmen, welche Überlastungen geeignete Kandidaten sind . Ein Kandidat ist anwendbar, wenn alle Argumente implizit in die formalen Parametertypen konvertierbar sind. Betrachten Sie diese vereinfachte Version Ihres Programms:
Gehen wir es also Zeile für Zeile durch.
Ich habe bereits besprochen, wie unglücklich das Wort "kompatibel" hier ist. Weitermachen. Wir fragen uns, ob bei der Überlastungsauflösung für Y (X) die Methodengruppe X in D1 konvertiert wird. Konvertiert es in D2?
So weit, ist es gut. X kann eine Methode enthalten, die auf die Argumentlisten von D1 oder D2 anwendbar ist.
Diese Zeile sagt wirklich nichts Interessantes.
Diese Linie ist faszinierend. Es bedeutet, dass es implizite Konvertierungen gibt, die jedoch in Fehler umgewandelt werden können! Dies ist eine bizarre Regel von C #. Um einen Moment abzuschweifen, hier ein Beispiel:
Eine Inkrementierungsoperation ist in einem Ausdrucksbaum unzulässig. Das Lambda kann jedoch weiterhin in den Ausdrucksbaumtyp konvertiert werden , auch wenn die Konvertierung jemals verwendet wird, handelt es sich um einen Fehler! Das Prinzip hier ist, dass wir möglicherweise die Regeln ändern möchten, die später in einen Ausdrucksbaum aufgenommen werden können. Durch Ändern dieser Regeln sollten die Typsystemregeln nicht geändert werden . Wir möchten Sie zwingen, Ihre Programme jetzt eindeutig zu gestalten , damit wir keine Änderungen an der Überlastungsauflösung einführen , wenn wir die Regeln für Ausdrucksbäume in Zukunft ändern, um sie zu verbessern .
Auf jeden Fall ist dies ein weiteres Beispiel für diese Art von bizarrer Regel. Eine Konvertierung kann zum Zwecke der Überlastungsauflösung vorhanden sein, ist jedoch ein Fehler bei der tatsächlichen Verwendung. Tatsächlich ist dies jedoch nicht genau die Situation, in der wir uns befinden.
Weiter geht's:
OK. Wir überlasten also X in Bezug auf D1. Die formale Parameterliste von D1 ist leer, daher überladen wir X () und freuen uns, dass wir eine Methode "string X ()" finden, die funktioniert. Ebenso ist die formale Parameterliste von D2 leer. Wieder finden wir, dass "string X ()" eine Methode ist, die auch hier funktioniert.
Das Prinzip hierbei ist, dass zur Bestimmung der Konvertierbarkeit von Methodengruppen die Auswahl einer Methode aus einer Methodengruppe mithilfe der Überlastauflösung erforderlich ist und bei der Überlastauflösung keine Rückgabetypen berücksichtigt werden .
Es gibt nur eine Methode in der Methodengruppe X, daher muss es die beste sein. Wir haben erfolgreich bewiesen, dass eine Konvertierung vorliegt aus X zu D1 und D2 von X nach.
Ist diese Zeile nun relevant?
Eigentlich nein, nicht in diesem Programm. Wir kommen nie so weit, diese Linie zu aktivieren. Denken Sie daran, dass wir hier versuchen, eine Überlastungsauflösung für Y (X) durchzuführen. Wir haben zwei Kandidaten Y (D1) und Y (D2). Beides gilt. Welches ist besser ? Nirgendwo in der Spezifikation beschreiben wir die Verbesserung zwischen diesen beiden möglichen Konvertierungen .
Nun könnte man sicherlich argumentieren, dass eine gültige Konvertierung besser ist als eine, die einen Fehler erzeugt. Das würde dann effektiv bedeuten, dass in diesem Fall die Überlastungsauflösung Rückgabetypen berücksichtigt, was wir vermeiden möchten. Die Frage ist dann, welches Prinzip besser ist: (1) Behalten Sie die Invariante bei, dass die Überlastungsauflösung keine Rückgabetypen berücksichtigt, oder (2) versuchen Sie, eine Konvertierung auszuwählen, von der wir wissen, dass sie über eine Konvertierung funktioniert, von der wir wissen, dass sie nicht funktioniert.
Dies ist ein Urteilsspruch. Mit lambda , wir tun den Rückgabetyp in dieser Art von Umsetzungen betrachten, in Abschnitt 7.4.3.3:
Es ist bedauerlich, dass Methodengruppen- und Lambda-Konvertierungen in dieser Hinsicht inkonsistent sind. Ich kann jedoch damit leben.
Wie auch immer, wir haben keine "Betterness" -Regel, um zu bestimmen, welche Umwandlung besser ist, X zu D1 oder X zu D2. Daher geben wir einen Mehrdeutigkeitsfehler bei der Auflösung von Y (X) an.
quelle
EDIT: Ich denke, ich habe es.
Wie Zinglon sagt, liegt dies daran, dass eine implizite Konvertierung von
GetString
nach erfolgtAction
, obwohl die Anwendung zur Kompilierungszeit fehlschlagen würde. Hier ist die Einführung zu Abschnitt 6.6 mit einigen Schwerpunkten (meiner):Jetzt war ich durch den ersten Satz verwirrt, der von einer Konvertierung in einen kompatiblen Delegatentyp spricht.
Action
ist kein kompatibler Delegat für eine Methode in derGetString
Methodengruppe, aber dieGetString()
Methode ist in ihrer normalen Form auf eine Argumentliste anwendbar, die unter Verwendung der Parametertypen und Modifikatoren von D erstellt wurde. Beachten Sie, dass dies nicht über den Rückgabetyp von spricht D. Deshalb wird es verwirrt ... weil es nur die Kompatibilität der DelegiertenGetString()
beim Anwenden der Konvertierung überprüft und nicht auf ihre Existenz überprüft.Ich denke, es ist lehrreich, die Überlastung kurz aus der Gleichung herauszulassen und zu sehen, wie sich dieser Unterschied zwischen der Existenz einer Konvertierung und ihrer Anwendbarkeit manifestieren kann. Hier ist ein kurzes, aber vollständiges Beispiel:
Keiner der Methodenaufrufausdrücke in
Main
Kompilierungen, aber die Fehlermeldungen sind unterschiedlich. Hier ist der fürIntMethod(GetString)
:Mit anderen Worten, Abschnitt 7.4.3.1 der Spezifikation kann keine zutreffenden Funktionselemente finden.
Hier ist der Fehler für
ActionMethod(GetString)
:Dieses Mal hat es die Methode ausgearbeitet, die es aufrufen möchte - aber es ist nicht gelungen, die erforderliche Konvertierung durchzuführen. Leider kann ich nicht herausfinden, wo diese letzte Prüfung durchgeführt wird - es sieht so aus, als wäre es in 7.5.5.1, aber ich kann nicht genau sehen, wo.
Alte Antwort entfernt, bis auf dieses bisschen - weil ich davon ausgehe, dass Eric das "Warum" dieser Frage beleuchten könnte ...
Sie suchen immer noch ... in der Zwischenzeit, wenn wir dreimal "Eric Lippert" sagen, denken Sie, wir werden einen Besuch bekommen (und damit eine Antwort)?
quelle
classWithSimpleMethods.GetString
undclassWithSimpleMethods.DoNothing
sind keine Delegierten?ClassWithSimpleMethods.GetString
nachAction
gültig ist? Damit eine MethodeM
mit einem Delegatentyp kompatibel istD
(§15.2), "existiert eine Identitäts- oder implizite Referenzkonvertierung vom RückgabetypM
in den Rückgabetyp vonD
."Die Verwendung von
Func<string>
undAction<string>
(offensichtlich sehr unterschiedlich zuAction
undFunc<string>
) inClassWithDelegateMethods
entfernt die Mehrdeutigkeit.Die Mehrdeutigkeit tritt auch zwischen
Action
und aufFunc<int>
.Ich bekomme auch den Mehrdeutigkeitsfehler damit:
Weitere Experimente zeigen, dass bei der Übergabe einer Methodengruppe an sich der Rückgabetyp bei der Bestimmung der zu verwendenden Überladung vollständig ignoriert wird.
quelle
Die Überladung mit
Func
undAction
ist verwandt (weil beide Delegierte sind) mitWenn Sie bemerken, weiß der Compiler nicht, welchen er aufrufen soll, da sie sich nur durch Rückgabetypen unterscheiden.
quelle
Func<string>
in einAction
... konvertieren können und Sie keine Methodengruppe konvertieren können, die nur aus einer Methode besteht, die einen String in einen vonAction
beiden zurückgibt .string
zu einem zurückkehrtAction
. Ich verstehe nicht, warum es Mehrdeutigkeiten gibt.