Warum sollte das Hinzufügen einer Methode einen mehrdeutigen Aufruf hinzufügen, wenn er nicht an der Mehrdeutigkeit beteiligt wäre?

112

Ich habe diese Klasse

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

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

Wenn ich es so nenne:

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

Es schreibt Normal Winnerin die Konsole.

Aber wenn ich eine andere Methode hinzufüge:

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

Ich erhalte folgende Fehlermeldung:

Der Aufruf ist zwischen den folgenden Methoden oder Eigenschaften nicht eindeutig:> ' Overloaded.ComplexOverloadResolution(params string[])' und ' Overloaded.ComplexOverloadResolution<string>(string)'

Ich kann verstehen, dass das Hinzufügen einer Methode zu einer Aufrufmehrdeutigkeit führen kann, aber es ist eine Mehrdeutigkeit zwischen den beiden bereits vorhandenen Methoden (params string[])und <string>(string)! Es ist klar, dass keine der beiden an der Mehrdeutigkeit beteiligten Methoden die neu hinzugefügte Methode ist, da die erste ein Parameter und die zweite eine generische ist.

Ist das ein Fehler? Welcher Teil der Spezifikation besagt, dass dies der Fall sein sollte?

McKay
quelle
2
Ich denke nicht, 'Overloaded.ComplexOverloadResolution(string)'bezieht sich auf die <string>(string)Methode; Ich denke, es bezieht sich auf die (string, object)Methode ohne Objekt geliefert.
Phoog
1
@phoog Oh, diese Daten wurden von StackOverflow geschnitten, weil es sich um ein Tag handelt, aber die Fehlermeldung enthält den Vorlagenbezeichner. Ich füge es wieder hinzu.
McKay
du hast mich erwischt! Ich habe die relevanten Abschnitte der Spezifikation in meiner Antwort zitiert, aber ich habe die letzte halbe Stunde nicht damit verbracht, sie zu lesen und zu verstehen!
Phoog
@phoog, wenn ich diese Teile der Spezifikation durchschaue, sehe ich nichts darüber, Mehrdeutigkeit in andere Methoden als sich selbst und eine andere Methode einzuführen, nicht zwei andere Methoden.
McKay
Mir ist aufgefallen, dass dies nur eine Stein-Papier-Schere ist : Jeder Satz von zwei unterschiedlichen Werten hat einen Gewinner, der vollständige Satz von drei Werten jedoch nicht.
Phoog

Antworten:

107

Ist das ein Fehler?

Ja.

Herzlichen Glückwunsch, Sie haben einen Fehler in der Überlastungsauflösung gefunden. Der Fehler wird in C # 4 und 5 reproduziert. es wird in der "Roslyn" -Version des semantischen Analysators nicht reproduziert. Ich habe das C # 5-Testteam informiert und hoffe, dass wir dies vor der endgültigen Veröffentlichung untersuchen und lösen können. (Wie immer keine Versprechen.)

Eine korrekte Analyse folgt. Die Kandidaten sind:

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

Kandidat Null ist offensichtlich nicht anwendbar, da er stringnicht in konvertierbar iststring[] . Das lässt drei.

Von den dreien müssen wir eine einzigartige beste Methode bestimmen. Dazu vergleichen wir die drei verbleibenden Kandidaten paarweise. Es gibt drei solche Paare. Alle von ihnen haben identisch Parameterlisten, sobald wir die ausgelassenen optionalen Parameter entfernt haben. Dies bedeutet, dass wir zu der in Abschnitt 7.5.3.2 der Spezifikation beschriebenen erweiterten Tiebreaking-Runde gehen müssen.

Welches ist besser, 1 oder 2? Der relevante Tiebreaker ist, dass eine generische Methode immer schlechter ist als eine nicht generische Methode. 2 ist schlechter als 1. 2 kann also nicht der Gewinner sein.

Welches ist besser, 1 oder 3? Der relevante Tiebreaker ist: Eine Methode, die nur in ihrer erweiterten Form anwendbar ist, ist immer schlechter als eine Methode, die in ihrer normalen Form anwendbar ist. Daher ist 1 schlechter als 3. Also kann 1 nicht der Gewinner sein.

Welches ist besser, 2 oder 3? Der relevante Tiebreaker ist, dass eine generische Methode immer schlechter ist als eine nicht generische Methode. 2 ist schlechter als 3. 2 kann also nicht der Gewinner sein.

Um aus einer Reihe von mehreren Kandidaten ausgewählt zu werden, muss ein Kandidat (1) ungeschlagen sein, (2) mindestens einen anderen Kandidaten schlagen und (3) der eindeutige Kandidat sein, der die ersten beiden Eigenschaften hat. Kandidat drei wird von keinem anderen Kandidaten geschlagen und schlägt mindestens einen anderen Kandidaten; Es ist der einzige Kandidat mit dieser Eigenschaft. Daher ist Kandidat drei der einzigartig beste Kandidat . Es sollte gewinnen.

Der C # 4-Compiler versteht es nicht nur falsch, da Sie richtig bemerken, dass er eine bizarre Fehlermeldung meldet. Dass der Compiler die Analyse der Überlastungsauflösung falsch macht, ist ein wenig überraschend. Dass die Fehlermeldung falsch ist, ist völlig überraschend. Die Fehlerheuristik "mehrdeutige Methode" wählt grundsätzlich zwei beliebige Methoden aus dem Kandidatensatz aus, wenn keine beste Methode ermittelt werden kann. Es ist nicht sehr gut, die "echte" Mehrdeutigkeit zu finden, wenn es tatsächlich eine gibt.

Man könnte vernünftigerweise fragen, warum das so ist. Es ist ziemlich schwierig, zwei Methoden zu finden , die "eindeutig mehrdeutig" sind, da die Beziehung "Betterness" intransitiv ist . Es ist möglich, Situationen zu finden, in denen Kandidat 1 besser als 2, 2 besser als 3 und 3 besser als 1 ist. In solchen Situationen können wir es nicht besser machen, als zwei davon als "die mehrdeutigen" auszuwählen.

Ich möchte diese Heuristik für Roslyn verbessern, aber sie hat eine niedrige Priorität.

(Übung für den Leser: "Entwickeln Sie einen linearen Zeitalgorithmus, um das eindeutige beste Mitglied einer Menge von n Elementen zu identifizieren, bei denen die Betterness-Beziehung intransitiv ist", war eine der Fragen, die mir am Tag meines Interviews für dieses Team gestellt wurden ein sehr harter Algorithmus; probieren Sie es aus.)

Einer der Gründe, warum wir das Hinzufügen optionaler Argumente zu C # so lange zurückgedrängt haben, war die Anzahl der komplexen mehrdeutigen Situationen, die in den Überlastungsauflösungsalgorithmus eingeführt werden. anscheinend haben wir es nicht richtig verstanden.

Wenn Sie ein Connect-Problem eingeben möchten, um es zu verfolgen, können Sie dies gerne tun. Wenn Sie nur möchten, dass wir darauf aufmerksam gemacht werden, halten Sie es für erledigt. Ich werde nächstes Jahr mit dem Testen fortfahren.

Vielen Dank, dass Sie mich darauf aufmerksam gemacht haben. Entschuldigung für den Fehler.

Eric Lippert
quelle
1
Danke für die Antwort. Sie sagten "1 ist schlechter als 2", aber es wählt Methode 1, wenn ich nur Methode 1 und 2 habe?
McKay
@ McKay: Hoppla, du hast recht, das habe ich rückwärts gesagt. Ich werde den Text korrigieren.
Eric Lippert
1
Es fühlt sich unangenehm an, den Satz "den Rest des Jahres" zu lesen, wenn man bedenkt, dass nicht einmal eine halbe Woche davon übrig ist :)
BoltClock
2
@BoltClock in der Tat bedeutet die Aussage "für den Rest des Jahres abreisen" einen freien Tag.
Phoog
1
Ich glaube schon. Ich las "3) sei der einzigartige Kandidat, der die ersten beiden Eigenschaften hat" als "sei der einzige Kandidat, der (ungeschlagen ist und mindestens einen anderen Kandidaten schlägt)" . Aber Ihr jüngster Kommentar lässt mich denken "(sei der einzige Kandidat, der ungeschlagen ist) und schlägt mindestens einen anderen Kandidaten" . Englisch könnte wirklich Gruppierungssymbole verwenden. Wenn letzteres zutrifft, bekomme ich es wieder.
default.kramer
5

Welcher Teil der Spezifikation besagt, dass dies der Fall sein sollte?

Abschnitt 7.5.3 (Überlastungsauflösung) sowie Abschnitte 7.4 (Mitgliedersuche) und 7.5.2 (Typinferenz).

Beachten Sie insbesondere Abschnitt 7.5.3.2 (besseres Funktionselement), in dem teilweise angegeben ist, dass "optionale Parameter ohne entsprechende Argumente aus der Parameterliste entfernt werden" und "Wenn M (p) eine nicht generische Methode ist und M (q) eine generische Methode, dann ist M (p) besser als M (q). "

Ich verstehe diese Teile der Spezifikation jedoch nicht gründlich genug, um zu wissen, welche Teile der Spezifikation dieses Verhalten steuern, geschweige denn, um zu beurteilen, ob es konform ist.

Phoog
quelle
Dies erklärt jedoch nicht, warum das Hinzufügen eines Mitglieds zu einer Mehrdeutigkeit zwischen zwei bereits vorhandenen Methoden führen würde.
McKay
@ McKay fair genug (siehe bearbeiten). Wir müssen nur auf Eric Lippert warten, um uns zu sagen, ob dies das richtige Verhalten ist: ->
Phoog
1
Das sind die richtigen Teile der Spezifikation. Das Problem ist, dass sie sagen, dass dies nicht der Fall sein sollte!
Eric Lippert
3

Sie können diese Mehrdeutigkeit vermeiden, indem Sie in einigen Methoden den Namen des ersten Parameters ändern und den Parameter angeben, den Sie zuweisen möchten

so was :

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
quelle
Oh, ich weiß, dass dieser Code schlecht ist, und es gibt verschiedene Möglichkeiten, ihn zu umgehen. Die Frage war: "Warum verhält sich der Compiler so?"
McKay
1

Wenn Sie die paramsvon Ihrer ersten Methode entfernen , würde dies nicht passieren. ComplexOverloadResolution(string)Ihre erste und dritte Methode haben beide gültige Aufrufe , aber wenn Ihre erste Methode ist, public void ComplexOverloadResolution(string[] something)gibt es keine Mehrdeutigkeit.

Wenn Sie einen Wert für einen Parameter object somethingElse = nullangeben, wird dieser zu einem optionalen Parameter und muss daher beim Aufrufen dieser Überladung nicht angegeben werden.

Edit: Der Compiler macht hier ein paar verrückte Sachen. Wenn Sie Ihre dritte Methode nach der ersten in Code verschieben, wird sie korrekt gemeldet. Es scheint also, dass die ersten beiden Überladungen genommen und gemeldet werden, ohne nach der richtigen zu suchen.

'ConsoleApplication1.Program.ComplexOverloadResolution (params string [])' und 'ConsoleApplication1.Program.ComplexOverloadResolution (string, object)'

Edit2: Neuer Befund. Wenn Sie eine der drei oben genannten Methoden entfernen, entsteht keine Mehrdeutigkeit zwischen den beiden. Es scheint also, dass der Konflikt nur auftritt, wenn drei Methoden vorhanden sind, unabhängig von der Reihenfolge.

Tomislav Markovski
quelle
Dies erklärt jedoch nicht, warum das Hinzufügen eines Mitglieds zu einer Mehrdeutigkeit zwischen zwei bereits vorhandenen Methoden führen würde.
McKay
Die Mehrdeutigkeit tritt zwischen Ihrer ersten und dritten Methode auf, aber warum der Compiler die beiden anderen meldet, ist mir ein Rätsel.
Tomislav Markovski
Aber wenn ich die zweite Methode entferne, habe ich keine Mehrdeutigkeit, sie ruft erfolgreich die dritte Methode auf. Es sieht also nicht so aus, als ob der Compiler eine Mehrdeutigkeit zwischen der ersten und der dritten Methode aufweist.
McKay
Siehe meine Bearbeitung. Verrückte Compiler.
Tomislav Markovski
Tatsächlich erzeugt jede Kombination von nur zwei Methoden keine Mehrdeutigkeit. Das ist sehr seltsam. Edit2.
Tomislav Markovski
1
  1. Wenn du schreibst

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

    oder einfach schreiben

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

    wird es endet in das gleiche Verfahren , in Verfahren

    public void ComplexOverloadResolution(params string[] something

    Dies ist ein paramsSchlüsselwort für die Ursache , mit dem es am besten auch für den Fall übereinstimmt, dass kein Parameter angegeben wurde

  2. Wenn Sie versuchen, Ihre neue Methode wie diese hinzuzufügen

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

    Diese Methode wird perfekt kompiliert und aufgerufen, da sie perfekt zu Ihrem Aufruf mit einem stringParameter passt . Dann viel stärker params string[] something.

  3. Sie deklarieren die zweite Methode wie Sie

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

    Compiler, springt in völliger Verwirrung zwischen der ersten Methode und dieser, hat gerade eine hinzugefügt. Weil es nicht weiß, welche Funktion er jetzt alle bei Ihrem Anruf haben soll

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

    Wenn Sie den Zeichenfolgenparameter wie einen folgenden Code aus dem Aufruf entfernen, wird alles korrekt kompiliert und funktioniert wie zuvor

    var blah = new Overloaded();
    blah.ComplexOverloadResolution(); // will be ComplexOverloadResolution(params string[] something) function called here, like a best match.
Tigran
quelle
Zuerst schreiben Sie zwei Anruffälle, die gleich sind. Also wird es natürlich in die gleiche Methode gehen, oder wolltest du etwas anderes schreiben?
McKay
Aber wenn ich den Rest Ihrer Antwort richtig verstehe, lesen Sie nicht, was der Compiler sagt, ist die Verwirrung, nämlich zwischen der ersten und der zweiten Methode, nicht der neuen dritten, die ich gerade hinzugefügt habe.
McKay
ah danke. Aber das lässt immer noch das Problem, das ich in meinem zweiten Kommentar zu Ihrem Beitrag erwähne.
McKay
Um es klarer zu machen, geben Sie an: "Compiler, springt in völliger Verwirrung zwischen der ersten Methode und dieser, hat gerade eine hinzugefügt." Aber es ist nicht so. Es springt verwirrt zu den beiden anderen Methoden. Die params-Methode und die generische Methode.
McKay
@ McKay: Nun, es ist ein Sprung in die Verwirrung, wenn man statedrei Funktionen hat, nicht einzelne oder einige, um genau zu sein. Tatsächlich reicht es aus, einen von ihnen zu kommentieren , um ein Problem zu lösen. Die beste Übereinstimmung zwischen den verfügbaren Funktionen ist die mit params, die zweite mit dem genericsParameter. Wenn wir die dritte hinzufügen, führt dies zu einer Verwirrung in einer setder Funktionen. Ich denke, es ist höchstwahrscheinlich keine klare Fehlermeldung, die vom Compiler erzeugt wird.
Tigran