Pourquoi l'ajout d'une méthode ajouterait un appel ambigu, s'il ne serait pas impliqué dans l'ambiguïté

112

J'ai cette classe

public class Overloaded
{
    public void ComplexOverloadResolution(params string[] something)
    {
        Console.WriteLine("Normal Winner");
    }

    public void ComplexOverloadResolution<M>(M something)
    {
        Console.WriteLine("Confused");
    }
}

Si je l'appelle comme ça:

        var blah = new Overloaded();
        blah.ComplexOverloadResolution("Which wins?");

Il écrit Normal Winnersur la console.

Mais, si j'ajoute une autre méthode:

    public void ComplexOverloadResolution(string something, object somethingElse = null)
    {
        Console.WriteLine("Added Later");
    }

J'obtiens l'erreur suivante:

L'appel est ambigu entre les méthodes ou propriétés suivantes:> ' Overloaded.ComplexOverloadResolution(params string[])' et ' Overloaded.ComplexOverloadResolution<string>(string)'

Je peux comprendre que l'ajout d'une méthode puisse introduire une ambiguïté d'appel, mais c'est une ambiguïté entre les deux méthodes qui existaient déjà (params string[])et <string>(string)! Il est clair qu'aucune des deux méthodes impliquées dans l'ambiguïté n'est la méthode nouvellement ajoutée, car la première est un paramètre et la seconde est un générique.

Est-ce un bug? Quelle partie de la spécification dit que cela devrait être le cas?

McKay
la source
2
Je ne pense pas qu'il se 'Overloaded.ComplexOverloadResolution(string)'réfère à la <string>(string)méthode; Je pense que cela fait référence à la (string, object)méthode sans objet fourni.
phoog
1
@phoog Oh, ces données ont été coupées par StackOverflow parce que c'est une balise, mais le message d'erreur a le désignateur de modèle. Je le rajoute.
McKay
vous m'avez attrapé! J'ai cité les sections pertinentes de la spécification dans ma réponse, mais je n'ai pas passé la dernière demi-heure à les lire et à les comprendre!
phoog
@phoog, en parcourant ces parties de la spécification, je ne vois rien sur l'introduction d'ambiguïté dans des méthodes autres que la sienne et une autre méthode, pas deux autres méthodes.
McKay
Il m'est venu à l'esprit qu'il ne s'agissait que de ciseaux à papier de roche : tout ensemble de deux valeurs distinctes a un gagnant, mais pas l'ensemble complet de trois valeurs.
phoog

Réponses:

107

Est-ce un bug?

Oui.

Félicitations, vous avez trouvé un bogue dans la résolution des surcharges. Le bogue se reproduit en C # 4 et 5; il ne se reproduit pas dans la version "Roslyn" de l'analyseur sémantique. J'ai informé l'équipe de test C # 5 et j'espère que nous pourrons faire enquête et résoudre ce problème avant la version finale. (Comme toujours, pas de promesses.)

Une analyse correcte suit. Les candidats sont:

0: C(params string[]) in its normal form
1: C(params string[]) in its expanded form
2: C<string>(string) 
3: C(string, object) 

Le candidat zéro est évidemment inapplicable car il stringn'est pas convertible en string[]. Cela en laisse trois.

Des trois, nous devons déterminer une meilleure méthode unique. Nous faisons cela en comparant par paires les trois candidats restants. Il existe trois paires de ce type. Tous ont des listes de paramètres identiques une fois que nous supprimons les paramètres facultatifs omis, ce qui signifie que nous devons passer à la ronde de départage avancée décrite dans la section 7.5.3.2 de la spécification.

Quel est le meilleur, 1 ou 2? Le bris d'égalité pertinent est qu'une méthode générique est toujours pire qu'une méthode non générique. 2 est pire que 1. Donc 2 ne peut pas être le gagnant.

Quel est le meilleur, 1 ou 3? Le bris d'égalité pertinent est: une méthode applicable uniquement sous sa forme développée est toujours pire qu'une méthode applicable sous sa forme normale. Donc 1 est pire que 3. Donc 1 ne peut pas être le gagnant.

Quel est le meilleur, 2 ou 3? Le bris d'égalité pertinent est qu'une méthode générique est toujours pire qu'une méthode non générique. 2 est pire que 3. Donc 2 ne peut pas être le gagnant.

Pour être choisi parmi un ensemble de plusieurs candidats applicables, un candidat doit être (1) invaincu, (2) battre au moins un autre candidat et (3) être le candidat unique qui possède les deux premières propriétés. Le troisième candidat n'est battu par aucun autre candidat et bat au moins un autre candidat; c'est le seul candidat avec cette propriété. Par conséquent, le troisième candidat est le meilleur candidat unique . Cela devrait gagner.

Non seulement le compilateur C # 4 se trompe-t-il, mais comme vous le notez correctement, il signale un message d'erreur bizarre. Le fait que le compilateur n'obtienne pas l'analyse de résolution de surcharge est un peu surprenant. Il n'est absolument pas surprenant qu'il obtienne le message d'erreur incorrect; l'heuristique d'erreur de "méthode ambiguë" choisit essentiellement deux méthodes quelconques de l'ensemble candidat si une meilleure méthode ne peut être déterminée. Il n'est pas très bon pour trouver la «vraie» ambiguïté, s'il y en a une.

On pourrait raisonnablement se demander pourquoi. Il est assez délicat de trouver deux méthodes qui soient "sans ambiguïté" car la relation "de bêtise" est intransitive . Il est possible de trouver des situations où le candidat 1 est meilleur que 2, 2 est meilleur que 3 et 3 est meilleur que 1. Dans de telles situations, nous ne pouvons pas faire mieux que d'en choisir deux comme "ambigus".

Je voudrais améliorer cette heuristique pour Roslyn mais c'est une faible priorité.

(Exercice pour le lecteur: "Concevoir un algorithme de temps linéaire pour identifier le meilleur membre unique d'un ensemble de n éléments où la relation d'amertume est intransitive" était l'une des questions qui m'ont été posées le jour où j'ai interviewé pour cette équipe. Ce n'est pas un algorithme très difficile; essayez-le.)

L'une des raisons pour lesquelles nous avons repoussé l'ajout d'arguments facultatifs à C # pendant si longtemps était le nombre de situations ambiguës complexes qu'il introduit dans l'algorithme de résolution de surcharge; apparemment, nous n'avons pas fait les choses correctement.

Si vous souhaitez saisir un problème Connect pour le suivre, n'hésitez pas. Si vous voulez simplement que cela soit porté à notre attention, considérez-le comme fait. Je ferai un suivi avec des tests l'année prochaine.

Merci d'avoir porté ceci à mon attention. Toutes mes excuses pour l'erreur.

Eric Lippert
la source
1
Merci pour la réponse. vous avez dit "1 est pire que 2", mais il choisit la méthode 1 si j'ai juste la méthode 1 et 2?
McKay
@McKay: Oups, vous avez raison, je l'ai dit à l'envers. Je vais corriger le texte.
Eric Lippert
1
Il est difficile de lire l'expression "le reste de l'année" étant donné qu'il ne reste même plus une demi-semaine :)
BoltClock
2
@BoltClock en effet, la déclaration "partir pour le reste de l'année" implique un jour de congé.
phoog
1
Je le pense. J'ai lu "3) être le candidat unique qui a les deux premières propriétés" comme "être le seul candidat qui (est invaincu et bat au moins un autre candidat)" . Mais votre commentaire le plus récent me fait penser "(soyez le seul candidat invaincu) et bat au moins un autre candidat" . L'anglais pourrait vraiment utiliser des symboles de regroupement. Si ce dernier est vrai, alors je le ressens.
default.kramer
5

Quelle partie de la spécification dit que cela devrait être le cas?

Section 7.5.3 (résolution de surcharge), ainsi que les sections 7.4 (recherche de membre) et 7.5.2 (inférence de type).

Notez en particulier la section 7.5.3.2 (meilleur membre de fonction), qui dit en partie "les paramètres optionnels sans arguments correspondants sont supprimés de la liste des paramètres" et "Si M (p) est une méthode non générique amd M (q) est méthode générique, alors M (p) est meilleur que M (q). "

Cependant, je ne comprends pas suffisamment ces parties de la spécification pour savoir quelles parties de la spécification contrôlent ce comportement, et encore moins pour juger de sa conformité.

phoog
la source
Mais cela n'explique pas pourquoi l'ajout d'un membre provoquerait une ambiguïté entre deux méthodes qui existaient déjà.
McKay
@McKay assez juste (voir modifier). Il faudra juste attendre qu'Eric Lippert nous dise si ce comportement est correct: ->
phoog
1
Ce sont les bonnes parties de la spécification. Le problème, c'est qu'ils disent que cela ne devrait pas être le cas!
Eric Lippert
3

vous pouvez éviter cette ambiguïté en changeant le nom du premier paramètre dans certaines méthodes et en spécifiant le paramètre que vous souhaitez affecter

comme ça :

public class Overloaded
{
    public void ComplexOverloadResolution(params string[] somethings)
    {
        Console.WriteLine("Normal Winner");
    }

    public void ComplexOverloadResolution<M>(M something)
    {
        Console.WriteLine("Confused");
    }

    public void ComplexOverloadResolution(string something, object somethingElse = null)
    {
        Console.WriteLine("Added Later");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Overloaded a = new Overloaded();
        a.ComplexOverloadResolution(something:"asd");
    }
}
MhdSyrwan
la source
Oh, je sais que ce code est mauvais, et il y a plusieurs façons de le contourner, la question était "pourquoi le compilateur se comporte-t-il de cette manière?"
McKay
1

Si vous supprimez le paramsde votre première méthode, cela ne se produira pas. La première et la troisième méthode ont les deux appels valides ComplexOverloadResolution(string), mais si votre première méthode existe, public void ComplexOverloadResolution(string[] something)il n'y aura aucune ambiguïté.

Fournir une valeur pour un paramètre en object somethingElse = nullfait un paramètre facultatif et il n'est donc pas nécessaire de le spécifier lors de l'appel de cette surcharge.

Edit: Le compilateur fait des trucs dingues ici. Si vous déplacez votre troisième méthode dans le code après la première, elle signalera correctement. Il semble donc qu'il prend les deux premières surcharges et les signale, sans vérifier la bonne.

«ConsoleApplication1.Program.ComplexOverloadResolution (chaîne de paramètres [])» et «ConsoleApplication1.Program.ComplexOverloadResolution (chaîne, objet)»

Edit2: Nouvelle découverte. La suppression de toute méthode parmi les trois ci-dessus ne produira aucune ambiguïté entre les deux. Il semble donc que le conflit n'apparaisse que si trois méthodes sont présentes, quel que soit l'ordre.

Tomislav Markovski
la source
Mais cela n'explique pas pourquoi l'ajout d'un membre provoquerait une ambiguïté entre deux méthodes qui existaient déjà.
McKay
L'ambiguïté se produit entre votre première et votre troisième méthode, mais pourquoi le compilateur signale-t-il les deux autres me dépasse.
Tomislav Markovski
Mais si je supprime la deuxième méthode, je n'ai pas d'ambiguïté, elle appelle avec succès la troisième méthode. Il ne semble donc pas que le compilateur ait une ambiguïté entre les première et troisième méthodes.
McKay
Voir mon Modifier. Compilateurs fous.
Tomislav Markovski
En fait, toute combinaison de deux méthodes seulement ne produit aucune ambiguïté. C'est très étrange. Modifier 2.
Tomislav Markovski
1
  1. Si vous écrivez

    var blah = new Overloaded();
    blah.ComplexOverloadResolution("Which wins?");

    ou écris simplement

    var blah = new Overloaded();
    blah.ComplexOverloadResolution();

    il aboutira à la même méthode , à la méthode

    public void ComplexOverloadResolution(params string[] something

    C'est le paramsmot clé de cause qui en fait la meilleure correspondance également pour le cas où aucun paramètre n'est spécifié

  2. Si vous essayez d'ajouter votre nouvelle méthode comme celle-ci

    public void ComplexOverloadResolution(string something)
    {
        Console.WriteLine("Added Later");
    }

    Il compilera et appellera parfaitement cette méthode car elle correspond parfaitement à votre appel avec un stringparamètre. Beaucoup plus fort alors params string[] something.

  3. Vous déclarez la deuxième méthode comme vous l'avez fait

    public void ComplexOverloadResolution(string something, object something=null);

    Compilateur, saute dans la confusion totale entre la première méthode et celle-ci, vient d'en ajouter une. Parce qu'il ne sait pas quelle fonction il devrait tous maintenant sur votre appel

    var blah = new Overloaded();
    blah.ComplexOverloadResolution("Which wins?");

    En fait, si vous supprimez le paramètre de chaîne de l'appel, comme un code suivant, tout se compile correctement et fonctionne comme avant

    var blah = new Overloaded();
    blah.ComplexOverloadResolution(); // will be ComplexOverloadResolution(params string[] something) function called here, like a best match.
Tigran
la source
Tout d'abord, vous écrivez deux cas d'appel identiques. Alors bien sûr, cela ira dans la même méthode, ou vouliez-vous écrire quelque chose de différent?
McKay
Mais encore une fois, si je comprends bien le reste de votre réponse, vous ne lisez pas ce que le compilateur dit est la confusion, à savoir entre la première et la deuxième méthode, pas la nouvelle troisième que je viens d'ajouter.
McKay
Ah merci. Mais cela laisse toujours le problème que je mentionne dans mon deuxième commentaire à votre message.
McKay
Pour être plus clair, vous dites "Compilateur, saute dans une confusion totale entre la première méthode et celle-ci, vient d'en ajouter une." Mais ce n'est pas le cas. Cela saute dans la confusion avec les deux autres méthodes. La méthode params et la méthode générique.
McKay
@McKay: bon, c'est un saut de confusion d'avoir une donnée statede 3 fonctions, pas une ou deux, pour être précis. En fait, il suffit de commenter n'importe lequel d'entre eux pour résoudre un problème. La meilleure correspondance parmi les fonctions disponibles est celle avec params, la seconde est celle avec genericsparamètre, lorsque nous ajoutons la troisième, cela crée une confusion dans l'une setdes fonctions. Je pense que ce n'est probablement pas un message d'erreur clair produit par le compilateur.
Tigran