J'ai un scénario dans lequel je souhaite utiliser la syntaxe du groupe de méthodes plutôt que des méthodes anonymes (ou la syntaxe lambda) pour appeler une fonction.
La fonction a deux surcharges, l'une qui prend un Action
, l'autre prend un Func<string>
.
Je peux heureusement appeler les deux surcharges en utilisant des méthodes anonymes (ou la syntaxe lambda), mais obtenir une erreur du compilateur d' invocation ambiguë si j'utilise la syntaxe de groupe de méthodes. Je peux contourner le problème par un casting explicite vers Action
ou Func<string>
, mais je ne pense pas que cela devrait être nécessaire.
Quelqu'un peut-il expliquer pourquoi les moulages explicites devraient être requis.
Exemple de code ci-dessous.
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() { }
}
Mise à jour C # 7.3
Selon le commentaire de 0xcde ci-dessous le 20 mars 2019 (neuf ans après avoir posté cette question!), Ce code se compile à partir de C # 7.3 grâce à des candidats de surcharge améliorés .
<LangVersion>7.3</LangVersion>
) ou version ultérieure grâce à des candidats de surcharge améliorés .Réponses:
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:
(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:
Alors passons en revue ligne par ligne.
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?
Jusqu'ici tout va bien. X peut contenir une méthode applicable avec les listes d'arguments de D1 ou D2.
Cette ligne ne dit vraiment rien d'intéressant.
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:
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:
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 .
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?
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:
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).
la source
EDIT: Je pense que je l'ai.
Comme le dit zinglon, c'est parce qu'il y a une conversion implicite de
GetString
enAction
même si l'application au moment de la compilation échouerait. Voici l'introduction à la section 6.6, avec un peu d'emphase (la mienne):Maintenant, j'étais confus par la première phrase - qui parle d'une conversion en un type de délégué compatible.
Action
n'est pas un délégué compatible pour aucune méthode duGetString
groupe de méthodes, mais laGetString()
méthode est applicable sous sa forme normale à une liste d'arguments construite à l'aide des types de paramètres et des modificateurs de D. Notez que ce n'est pas le cas parle du type de retour de D. C'est pourquoi cela devient confus ... parce qu'il vérifierait uniquement la compatibilité des déléguésGetString()
lors de l' application de la conversion, sans vérifier son existence.Je pense qu'il est instructif de laisser brièvement la surcharge hors de l'équation et de voir comment cette différence entre l' existence d' une conversion et son applicabilité peut se manifester. Voici un exemple court mais complet:
Aucune des expressions d'appel de méthode dans les
Main
compilations, mais les messages d'erreur sont différents. Voici celui pourIntMethod(GetString)
:En d'autres termes, la section 7.4.3.1 de la spécification ne trouve aucun membre de fonction applicable.
Voici maintenant l'erreur pour
ActionMethod(GetString)
:Cette fois, il a déterminé la méthode qu'il souhaite appeler - mais il n'a pas réussi à effectuer la conversion requise. Malheureusement, je ne peux pas trouver le détail de la spécification où cette vérification finale est effectuée - il semble que cela pourrait être dans 7.5.5.1, mais je ne peux pas voir exactement où.
Ancienne réponse supprimée, à l'exception de ce bit - car je pense qu'Eric pourrait éclairer le "pourquoi" de cette question ...
Toujours à la recherche ... en attendant, si on dit "Eric Lippert" trois fois, pensez-vous que nous aurons une visite (et donc une réponse)?
la source
classWithSimpleMethods.GetString
etclassWithSimpleMethods.DoNothing
ne sont pas des délégués?ClassWithSimpleMethods.GetString
àAction
est valide? Pour qu'une méthodeM
soit compatible avec un type déléguéD
(§15.2) "une identité ou une conversion de référence implicite existe du type deM
retour de au type de retour deD
."Utiliser
Func<string>
etAction<string>
(évidemment très différent deAction
etFunc<string>
) dans leClassWithDelegateMethods
supprime l'ambiguïté.L'ambiguïté se produit également entre
Action
etFunc<int>
.J'obtiens également l'erreur d'ambiguïté avec ceci:
Une expérimentation plus poussée montre que lors du passage d'un groupe de méthodes par lui-même, le type de retour est complètement ignoré lors de la détermination de la surcharge à utiliser.
la source
La surcharge avec
Func
etAction
s'apparente (car les deux sont des délégués) àSi vous remarquez, le compilateur ne sait pas lequel appeler car ils ne diffèrent que par les types de retour.
la source
Func<string>
en unAction
... et vous ne pouvez pas convertir un groupe de méthodes composé uniquement d'une méthode qui renvoie une chaîne en unAction
non plus.string
à unAction
. Je ne vois pas pourquoi il y a ambiguïté.