Tout d'abord, permettez-moi de dire que la réponse de Jon est correcte. C'est l'une des parties les plus poilues de la spécification, donc bon Jon pour y plonger la tête la première.
Deuxièmement, laissez-moi dire que cette ligne:
Une conversion implicite existe d'un groupe de méthodes vers un type de délégué compatible
(je souligne) est profondément trompeur et malheureux. Je vais avoir une discussion avec Mads sur la suppression du mot «compatible» ici.
La raison pour laquelle cela est trompeur et malheureux est qu'il semble que cela appelle à la section 15.2, «Compatibilité des délégués». La section 15.2 décrit la relation de compatibilité entre les méthodes et les types de délégués , mais il s'agit d'une question de convertibilité des groupes de méthodes et des types de délégués , ce qui est différent.
Maintenant que nous avons éliminé cela, nous pouvons parcourir la section 6.6 de la spécification et voir ce que nous obtenons.
Pour résoudre les surcharges, nous devons d'abord déterminer quelles surcharges sont des candidats applicables . Un candidat est applicable si tous les arguments sont implicitement convertibles en types de paramètres formels. Considérez cette version simplifiée de votre programme:
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);
}
}
Alors passons en revue ligne par ligne.
Une conversion implicite existe à partir d'un groupe de méthodes vers un type de délégué compatible.
J'ai déjà expliqué comment le mot "compatible" est malheureux ici. Passer à autre chose. Nous nous demandons lors de la résolution de surcharge sur Y (X), le groupe de méthodes X se convertit-il en D1? Se transforme-t-il en D2?
Étant donné un délégué de type D et une expression E classée comme groupe de méthodes, une conversion implicite existe de E en D si E contient au moins une méthode applicable [...] à une liste d'arguments construite à l'aide du paramètre types et modificateurs de D, comme décrit ci-dessous.
Jusqu'ici tout va bien. X peut contenir une méthode applicable avec les listes d'arguments de D1 ou D2.
L'application au moment de la compilation d'une conversion d'un groupe de méthodes E en un délégué de type D est décrite ci-dessous.
Cette ligne ne dit vraiment rien d'intéressant.
Notez que l'existence d'une conversion implicite de E en D ne garantit pas que l'application à la compilation de la conversion réussira sans erreur.
Cette ligne est fascinante. Cela signifie qu'il y a des conversions implicites qui existent, mais qui sont susceptibles d'être transformées en erreurs! C'est une règle bizarre de C #. Pour faire une digression un instant, voici un exemple:
void Q(Expression<Func<string>> f){}
string M(int x) { ... }
...
int y = 123;
Q(()=>M(y++));
Une opération d'incrémentation est illégale dans une arborescence d'expression. Cependant, le lambda est toujours convertible en type d'arborescence d'expression, même si si la conversion est déjà utilisée, c'est une erreur! Le principe ici est que nous pourrions vouloir changer les règles de ce qui peut aller dans un arbre d'expression plus tard; la modification de ces règles ne doit pas modifier les règles du système de types . Nous voulons vous obliger à rendre vos programmes sans ambiguïté maintenant , de sorte que lorsque nous changerons les règles des arbres d'expression à l'avenir pour les améliorer, nous n'introduisons pas de changements de rupture dans la résolution des surcharges .
Quoi qu'il en soit, c'est un autre exemple de ce genre de règle bizarre. Une conversion peut exister à des fins de résolution de surcharge, mais il s'agit d'une erreur à utiliser réellement. Bien qu'en fait, ce ne soit pas exactement la situation dans laquelle nous nous trouvons ici.
Passer à autre chose:
Une seule méthode M est sélectionnée correspondant à une invocation de méthode de la forme E (A) [...] La liste d'arguments A est une liste d'expressions, chacune classée comme une variable [...] du paramètre correspondant au format formel -liste-des-paramètres de D.
D'ACCORD. Nous faisons donc une résolution de surcharge sur X par rapport à D1. La liste des paramètres formels de D1 est vide, donc nous faisons une résolution de surcharge sur X () et joy, nous trouvons une méthode "string X ()" qui fonctionne. De même, la liste des paramètres formels de D2 est vide. Encore une fois, nous trouvons que "string X ()" est une méthode qui fonctionne ici aussi.
Le principe ici est que la détermination de la convertibilité d'un groupe de méthodes nécessite la sélection d'une méthode dans un groupe de méthodes utilisant la résolution de surcharge , et la résolution de surcharge ne prend pas en compte les types de retour .
Si l'algorithme [...] produit une erreur, une erreur de compilation se produit. Sinon, l'algorithme produit une seule meilleure méthode M ayant le même nombre de paramètres que D et la conversion est considérée comme existante.
Il n'y a qu'une seule méthode dans le groupe de méthodes X, elle doit donc être la meilleure. Nous avons prouvé avec succès qu'il existe une conversion de X vers D1 et de X vers D2.
Maintenant, cette ligne est-elle pertinente?
La méthode sélectionnée M doit être compatible avec le type de délégué D, sinon, une erreur de compilation se produit.
En fait, non, pas dans ce programme. Nous n'allons jamais jusqu'à activer cette ligne. Parce que, rappelez-vous, ce que nous faisons ici, c'est essayer de faire une résolution de surcharge sur Y (X). Nous avons deux candidats Y (D1) et Y (D2). Les deux sont applicables. Quel est le meilleur ? Nulle part dans la spécification nous ne décrivons l'amertume entre ces deux conversions possibles .
Maintenant, on pourrait certainement affirmer qu'une conversion valide est meilleure qu'une conversion qui produit une erreur. Cela signifierait alors effectivement, dans ce cas, que la résolution de surcharge prend en compte les types de retour, ce que nous voulons éviter. La question est alors de savoir quel principe est le meilleur: (1) maintenir l'invariant selon lequel la résolution de surcharge ne prend pas en compte les types de retour, ou (2) essayer de choisir une conversion dont nous savons qu'elle fonctionnera sur une conversion que nous savons qu'elle ne fonctionnera pas?
C'est un appel au jugement. Avec lambdas , nous faisons considérer le type de retour dans ce genre de conversions, dans la section 7.4.3.3:
E est une fonction anonyme, T1 et T2 sont des types délégués ou des types d'arbres d'expression avec des listes de paramètres identiques, un type de retour déduit X existe pour E dans le contexte de cette liste de paramètres, et l'un des éléments suivants est maintenu:
T1 a un type de retour Y1 et T2 a un type de retour Y2, et la conversion de X en Y1 est meilleure que la conversion de X en Y2
T1 a un type de retour Y et T2 est nul retour
Il est regrettable que les conversions de groupes de méthodes et les conversions lambda soient incohérentes à cet égard. Cependant, je peux vivre avec.
Quoi qu'il en soit, nous n'avons pas de règle «d'amertume» pour déterminer quelle conversion est la meilleure, X en D1 ou X en D2. Nous donnons donc une erreur d'ambiguïté sur la résolution de Y (X).