104 votes

Compilateur Ambigu invocation d'erreur - anonyme méthode et la méthode de groupe avec la touche Func<> ou de l'Action

J'ai un scénario où je veux utiliser la méthode syntaxe du groupe plutôt que les méthodes anonymes (ou lambda syntaxe) pour l'appel d'une fonction.

La fonction a deux surcharges, celui qui prend un Action, l'autre prend un Func<string>.

Je peux heureusement appeler les deux surcharges à l'aide de méthodes anonymes (ou lambda syntaxe), mais à obtenir une erreur du compilateur, de l' Ambigu invocation si j'utilise la méthode du groupe de syntaxe. Je peux contournement par le cast explicite à l' Action ou Func<string>, mais ne pense pas que ce soit nécessaire.

Quelqu'un peut-il expliquer pourquoi les conversions explicites devraient être tenus.

L'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() { }
}

100voto

Eric Lippert Points 300275

Tout d'abord, laissez-moi juste dire que Jon réponse est bonne. C'est l'un des bien poilue parties de la spécification, si bon sur Jon pour plonger dedans la tête la première.

Deuxièmement, permettez-moi de dire que cette ligne:

Une conversion implicite existe à partir d'une méthode de groupe pour un compatible type délégué

(emphase ajoutée) est profondément trompeuse et malheureux. Je vais avoir une conversation avec Mads sur d'obtenir le mot "compatible" supprimé ici.

La raison pour cela est trompeur et malheureux, c'est parce qu'on dirait que c'est appeler à l'article 15.2, "Délégué de compatibilité". La Section 15.2 décrit la compatibilité de la relation entre les méthodes et les types délégués, mais c'est une question de la convertibilité de la méthode des groupes et des types délégués, ce qui est différent.

Maintenant que nous avons obtenu que sur le chemin, nous pouvons marcher à travers la section 6.6 de la spécification et de voir ce que nous obtenons.

Faire de résolution de surcharge, nous devons d'abord déterminer les surcharges sont applicables candidats. Un candidat est applicable que si tous les arguments sont implicitement convertible dans le paramètre formel types. Considérer 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);
    }
}

Donc, nous allons aller ligne par ligne.

Une conversion implicite existe à partir d'une méthode de groupe compatible type de délégué.

J'ai déjà discuté de la façon dont le mot "compatible" est malheureux ici. Aller de l'avant. Nous nous demandons quand on fait de la résolution de surcharge sur Y(X) méthode de groupe X convertir en D1? Est-il convertir de D2?

Étant donné un délégué de type D et un l'expression de E qui est classé comme un méthode de groupe, une conversion implicite il existe de E à D si E contient au moins une méthode qui s'applique [...] à une liste d'arguments construits par l'utilisation de les types de paramètres et les modificateurs de D, comme décrit ci-après.

So far So good. X peut contenir une méthode qui est applicable avec l'argument des listes de D1 ou D2.

Le moment de la compilation de l'application d'une conversion à partir d'une méthode de groupe E à un délégué de type D est décrite ci-après.

Cette ligne n'a pas vraiment dire quelque chose d'intéressant.

Veuillez noter que l'existence d'une conversion implicite de E à D ne garantit pas que le moment de la compilation de l'application de la conversion réussira sans erreur.

Cette ligne est fascinant. Cela signifie qu'il y a des conversions implicites qui existent, mais qui sont susceptibles d'être transformé en erreurs! C'est bizarre ce que la règle de C#. Pour m'égare un moment, 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égal dans une arborescence d'expression. Cependant, le lambda est toujours convertible à l'expression type d'arbre, même si la conversion est jamais utilisé, c'est une erreur! Le principe ici est que nous pourrions vouloir changer les règles de ce qui peut aller dans une arborescence d'expression plus tard; la modification de ces règles ne devraient pas changer le type de système de règles. Nous voulons vous forcer à faire de vos programmes sans ambiguïté maintenant, de sorte que lorsque nous changer les règles pour les arbres d'expression dans l'avenir de faire de leur mieux, à nous de ne pas introduire de rupture des changements dans la résolution de surcharge.

De toute façon, c'est un autre exemple de cette sorte de bizarre à la règle. Une conversion peut exister pour les fins de la résolution de surcharge, mais être une erreur d'utiliser effectivement. Bien que, en fait, ce n'est pas exactement la situation dans laquelle nous sommes ici.

Aller de l'avant:

Une seule méthode M est sélectionné correspondant à une invocation de méthode de la forme E(A) [...] La liste des arguments est une liste d'expressions, chaque classés en tant que variable [...] du paramètre correspondant dans le système-paramètres-liste de D.

OK. Donc, nous faisons de la résolution de surcharge sur X par rapport à D1. Le paramètre formel de la liste de D1 est vide, donc nous n'résolution de surcharge sur X() et de la joie, nous trouvons une méthode "chaîne X()" qui fonctionne. De même, le paramètre formel de la liste de D2 est vide. Encore une fois, nous constatons que la "chaîne X()" est une méthode qui fonctionne ici aussi.

Ici, le principe est que la détermination de la méthode de groupe de la convertibilité nécessite la sélection d'une méthode à partir d'une méthode de groupe à l'aide de la résolution de surcharge, et la résolution de surcharge ne considère pas les types de retour.

Si l'algorithme de [...] produit une erreur, une erreur de compilation se produit. Sinon, l'algorithme produit une meilleure méthode de M avoir le même nombre de paramètres que D et la conversion est réputée exister.

Il n'y a qu'une seule méthode dans la méthode du groupe de X, de sorte qu'il doit être le meilleur. Nous avons prouvé avec succès que la conversion existe de X à D1 et à partir de X à D2.

Maintenant, est-ce la ligne est-il pertinent?

La méthode sélectionnée M doit être compatible avec le délégué de type D, ou sinon, une erreur de compilation se produit.

En fait, non, pas dans ce programme. Nous n'avons jamais aller aussi loin que l'activation de cette ligne. Car, rappelons-le, ce que nous faisons ici est d'essayer de faire de la résolution de surcharge sur Y(X). Nous avons deux candidats Y(D1) et Y(D2). Les deux sont applicables. Qui est mieux? Nulle part dans le cahier des charges ne nous décrivons betterness entre ces deux conversions possibles.

Maintenant, on pourrait certainement faire valoir que la conversion est meilleur que celui qui produit une erreur. Ce serait alors effectivement dire que, dans ce cas, la résolution de surcharge NE tient pas compte des types de retour, ce qui est quelque chose que nous voulons éviter. La question est alors, le principe est le meilleur: (1) maintenir l'invariant que la résolution de surcharge ne considère pas les types de retour, ou (2) essayez de choisir un de conversion, nous le savons, le travail de plus d'un, nous le savons, non?

C'est un appel de jugement. Avec les lambdas, nous faire considérer le type de retour dans ces sortes 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 de l'expression de l'arbre les types des paramètres sont identiques listes, une déduit le type de retour de X existe pour l'E dans le contexte de la liste des paramètres, et l'une des opérations suivantes:

  • T1 a un type de retour Y1, et T2 a un type de retour Y2, et de la conversion de X à Y1 est mieux que le la conversion de X Y2

  • T1 a un type de retour Y, et T2 est nulle retour

Il est regrettable que la méthode du groupe de conversions et de lambda conversions sont contradictoires dans ce domaine. Cependant, je peux vivre avec ça.

De toute façon, nous n'avons pas de "betterness" la règle pour déterminer la conversion est mieux, X D1 X D2. Par conséquent, nous donnons une erreur d'ambiguïté sur la résolution de Y(X).

36voto

Jon Skeet Points 692016

EDIT: je pense que je l'ai.

Comme zinglon dit, c'est parce qu'il y a une conversion implicite de GetString de Action , même si le moment de la compilation de l'application serait un échec. Voici l'introduction à la section 6.6, avec une certaine emphase (le mien):

Une conversion implicite (§6.1) existe à partir d'une méthode de groupe (§7.1) pour un compatible type de délégué. Donné un délégué de type D et une expression E qui est classé comme une méthode de groupe, une conversion implicite existe à partir de l'E pour D si E contient au moins une méthode qui est applicable dans sa forme normale (§7.4.3.1) à une liste d'arguments construit par l'utilisation du paramètre les types et les modificateurs de D, comme décrit dans ce qui suit.

Maintenant, j'ai été confus par la première phrase qui parle d'une conversion à une compatible type de délégué. Action n'est pas compatible délégué pour n'importe quelle méthode de l' GetString méthode de groupe, mais l' GetString() méthode est applicable dans sa forme normale à une liste d'arguments construits par l'utilisation des types de paramètre et les modificateurs de D. Notez que ce n'est pas parler du type de retour de D. c'est pourquoi il est confus... parce que ce serait seulement le délégué de la compatibilité de l' GetString() lors de l'application de la conversion, pas de vérification de son existence.

Je pense qu'il est intéressant de laisser la surcharge de l'équation brièvement, et de voir comment cette différence entre une conversion de l' existence et de son applicabilité peut se manifester. Voici une courte mais complète exemple:

using System;

class Program
{
    static void ActionMethod(Action action) {}
    static void IntMethod(int x) {}

    static string GetString() { return ""; }

    static void Main(string[] args)
    {
        IntMethod(GetString);
        ActionMethod(GetString);
    }
}

Ni l'un ni l'invocation de la méthode des expressions en Main compile, mais les messages d'erreur sont différents. Voici l'un pour l' IntMethod(GetString):

Test.cs(12,9): erreur CS1502: Le meilleur méthode surchargée de match pour 'Programme.IntMethod(int)' a certains les arguments non valides

En d'autres termes, l'article 7.4.3.1 de la spec ne pouvez pas trouver applicables en fonction des membres.

Maintenant, voici l'erreur d' ActionMethod(GetString):

Test.cs(13,22): erreur CS0407: 'string Programme.GetString()' a la mauvaise le type de retour

Cette fois, il a travaillé sur la méthode qu'il veut appeler - mais il n'a pas pu ensuite effectuer la conversion. Malheureusement je ne peux pas trouver les bits de la spec, où que le contrôle final est effectué, il semble qu'il pourrait être dans 7.5.5.1, mais je ne vois pas où exactement.


Vieille réponse supprimées, à l'exception de ce bit - parce que j'attends de Eric pourrait faire la lumière sur le "pourquoi" de cette question...

Toujours à la recherche... dans le temps de le dire, si nous disons "Eric Lippert" trois fois, pensez-vous que nous allons recevoir la visite (et donc une réponse)?

1voto

Matt Ellen Points 5270

À l'aide de Func<string> et Action<string> (évidemment très différentes de Action et Func<string>) en ClassWithDelegateMethods élimine l'ambiguïté.

L'ambiguïté se produit également entre les Action et Func<int>.

Je reçois aussi l'erreur d'ambiguïté avec ceci:

class Program
{ 
    static void Main(string[] args) 
    { 
        ClassWithSimpleMethods classWithSimpleMethods = new ClassWithSimpleMethods(); 
        ClassWithDelegateMethods classWithDelegateMethods = new ClassWithDelegateMethods(); 

        classWithDelegateMethods.Method(classWithSimpleMethods.GetOne);
    } 
} 

class ClassWithDelegateMethods 
{ 
    public void Method(Func<int> func) { /* do something */ }
    public void Method(Func<string> func) { /* do something */ } 
}

class ClassWithSimpleMethods 
{ 
    public string GetString() { return ""; } 
    public int GetOne() { return 1; }
} 

Poursuite de l'expérimentation montre que lors du passage dans une méthode de groupe par lui-même, le type de retour est complètement ignorée lors de la détermination de la surcharge à utiliser.

class Program
{
    static void Main(string[] args)
    {
        ClassWithSimpleMethods classWithSimpleMethods = new ClassWithSimpleMethods();
        ClassWithDelegateMethods classWithDelegateMethods = new ClassWithDelegateMethods();

        //The call is ambiguous between the following methods or properties: 
        //'test.ClassWithDelegateMethods.Method(System.Func<int,int>)' 
        //and 'test.ClassWithDelegateMethods.Method(test.ClassWithDelegateMethods.aDelegate)'
        classWithDelegateMethods.Method(classWithSimpleMethods.GetX);
    }
}

class ClassWithDelegateMethods
{
    public delegate string aDelegate(int x);
    public void Method(Func<int> func) { /* do something */ }
    public void Method(Func<string> func) { /* do something */ }
    public void Method(Func<int, int> func) { /* do something */ }
    public void Method(Func<string, string> func) { /* do something */ }
    public void Method(aDelegate ad) { }
}

class ClassWithSimpleMethods
{
    public string GetString() { return ""; }
    public int GetOne() { return 1; }
    public string GetX(int x) { return x.ToString(); }
} 

0voto

Daniel A. White Points 91889

La surcharge avec Func et Action est comparable (parce que les deux d'entre eux sont des délégués)

string Function() // Func<string>
{
}

void Function() // Action
{
}

Si vous remarquez, le compilateur ne sait pas qui appeler parce qu'ils ne diffèrent que par le type de retour.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X