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:
Es besteht eine implizite Konvertierung von einer Methodengruppe in einen kompatiblen Delegattyp
(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:
class Program
{
delegate void D1();
delegate string D2();
static string X() { return null; }
static void Y(D1 d1) {}
static void Y(D2 d2) {}
static void Main()
{
Y(X);
}
}
Gehen wir es also Zeile für Zeile durch.
Es besteht eine implizite Konvertierung von einer Methodengruppe in einen kompatiblen Delegattyp.
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?
Bei einem Delegatentyp D und einem Ausdruck E, der als Methodengruppe klassifiziert ist, besteht eine implizite Konvertierung von E nach D, wenn E mindestens eine Methode enthält, die [...] auf eine unter Verwendung des Parameters erstellte Argumentliste anwendbar ist Typen und Modifikatoren von D, wie im Folgenden beschrieben.
So weit, ist es gut. X kann eine Methode enthalten, die auf die Argumentlisten von D1 oder D2 anwendbar ist.
Die Anwendung einer Konvertierung von einer Methodengruppe E zu einem Delegatentyp D zur Kompilierungszeit wird im Folgenden beschrieben.
Diese Zeile sagt wirklich nichts Interessantes.
Beachten Sie, dass das Vorhandensein einer impliziten Konvertierung von E nach D nicht garantiert, dass die Anwendung der Konvertierung zur Kompilierungszeit ohne Fehler erfolgreich ist.
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:
void Q(Expression<Func<string>> f){}
string M(int x) { ... }
...
int y = 123;
Q(()=>M(y++));
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:
Eine einzelne Methode M wird entsprechend einem Methodenaufruf der Form E (A) ausgewählt. [...] Die Argumentliste A ist eine Liste von Ausdrücken, die jeweils als Variable [...] des entsprechenden Parameters im Formal klassifiziert sind -Parameter-Liste von D.
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 .
Wenn der Algorithmus [...] einen Fehler erzeugt, tritt ein Fehler bei der Kompilierung auf. Andernfalls erzeugt der Algorithmus eine einzelne beste Methode M mit der gleichen Anzahl von Parametern wie D, und die Umwandlung wird als vorhanden angesehen.
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?
Die ausgewählte Methode M muss mit dem Delegatentyp D kompatibel sein. Andernfalls tritt ein Fehler bei der Kompilierung auf.
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:
E ist eine anonyme Funktion, T1 und T2 sind Delegiertypen oder Ausdrucksbaumtypen mit identischen Parameterlisten, ein abgeleiteter Rückgabetyp X existiert für E im Kontext dieser Parameterliste und einer der folgenden Punkte gilt:
T1 hat einen Rückgabetyp Y1 und T2 hat einen Rückgabetyp Y2, und die Konvertierung von X nach Y1 ist besser als die Konvertierung von X nach Y2
T1 hat einen Rückgabetyp Y und T2 ist nichtig
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.