c# - Perché aggiungere un metodo aggiunge una chiamata ambigua se non è coinvolta nell'ambiguità




overload-resolution (5)

Ho questa classe

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

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

Se lo chiamo in questo modo:

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

Scrive Normal Winner sulla console.

Ma, se aggiungo un altro metodo:

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

Ottengo il seguente errore:

La chiamata è ambigua tra i seguenti metodi o proprietà:> ' Overloaded.ComplexOverloadResolution(params string[]) ' e ' Overloaded.ComplexOverloadResolution<string>(string) '

Posso capire che l'aggiunta di un metodo potrebbe introdurre un'ambiguità di chiamata, ma è un'ambiguità tra i due metodi già esistenti (params string[]) e <string>(string) ! Chiaramente, nessuno dei due metodi coinvolti nell'ambiguità è il metodo appena aggiunto, perché il primo è un param, e il secondo è un generico.

è un insetto? Quale parte della specifica dice che dovrebbe essere così?


Answers

Se rimuovi i params dal tuo primo metodo, questo non succederebbe. Il primo e il terzo metodo hanno entrambe chiamate valide ComplexOverloadResolution(string) , ma se il primo metodo è public void ComplexOverloadResolution(string[] something) non ci sarà alcuna ambiguità.

Fornire valore per un object somethingElse = null parametro object somethingElse = null rende parametro opzionale e quindi non deve essere specificato quando si chiama quel sovraccarico.

Modifica: il compilatore sta facendo alcune cose pazzesche qui. Se sposti il ​​tuo terzo metodo in codice dopo il tuo primo, verrà segnalato correttamente. Quindi sembra che stia prendendo i primi due sovraccarichi e li segnali, senza verificare quello corretto.

"ConsoleApplication1.Program.ComplexOverloadResolution (params string [])" e "ConsoleApplication1.Program.ComplexOverloadResolution (string, object)"

Edit2: nuova scoperta. La rimozione di qualsiasi metodo dai tre precedenti non produrrà alcuna ambiguità tra i due. Quindi sembra che il conflitto appaia solo se ci sono tre metodi presenti, indipendentemente dall'ordine.


Quale parte della specifica dice che dovrebbe essere così?

Sezione 7.5.3 (risoluzione di sovraccarico), insieme alle sezioni 7.4 (ricerca dei membri) e 7.5.2 (inferenza del tipo).

Nota in particolare la sezione 7.5.3.2 (membro della funzione migliore), che dice in parte "i parametri opzionali senza argomenti corrispondenti vengono rimossi dalla lista dei parametri" e "Se M (p) è un metodo non generico amd M (q) è un metodo generico, quindi M (p) è migliore di M (q). "

Tuttavia, non capisco abbastanza bene queste parti delle specifiche per sapere quali parti della specifica controllano questo comportamento, e tanto meno per giudicare se sia conforme.


  1. Se scrivi

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

    o semplicemente scrivi

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

    finirà nello stesso metodo , nel metodo

    public void ComplexOverloadResolution(params string[] something
    

    Questa è la causa della parola chiave params che ne fa la migliore corrispondenza anche nel caso in cui nessun parametro specificato

  2. Se provi ad aggiungere un nuovo metodo come questo

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

    Si compila perfettamente e chiama questo metodo in quanto è perfetto per la tua chiamata con un parametro string . Molto più forte di params string[] something .

  3. Tu dichiari il secondo metodo come hai fatto tu

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

    Compilatore, salta in totale confusione tra il primo metodo e questo, ne ha appena aggiunto uno. Perché non sa quale funzione dovrebbe fare ora per la tua chiamata

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

    Infatti, se rimuovi il parametro stringa dalla chiamata, come un codice seguente, tutto viene compilato correttamente e funziona come prima

    var blah = new Overloaded();
    blah.ComplexOverloadResolution(); // will be ComplexOverloadResolution(params string[] something) function called here, like a best match.
    

è un insetto?

Sì.

Congratulazioni, hai trovato un bug nella risoluzione di sovraccarico. Il bug si riproduce in C # 4 e 5; non si riproduce nella versione "Roslyn" dell'analizzatore semantico. Ho informato il team di test C # 5, e speriamo di poterlo investigare e risolvere prima della versione finale. (Come sempre, nessuna promessa.)

Segue un'analisi corretta. I candidati sono:

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

Lo zero candidato è ovviamente inapplicabile perché la string non è convertibile in string[] . Ne rimangono tre.

Dei tre, dobbiamo determinare un metodo unico e migliore. Lo facciamo facendo comparazioni a coppie dei tre candidati rimanenti. Ci sono tre di queste coppie. Tutti hanno elenchi di parametri identici una volta che rimuoviamo i parametri facoltativi omessi, il che significa che dobbiamo andare al round tie-break avanzato descritto nella sezione 7.5.3.2 della specifica.

Quale è meglio, 1 o 2? Il tie breaker rilevante è che un metodo generico è sempre peggio di un metodo non generico. 2 è peggio di 1. Quindi 2 non può essere il vincitore.

Quale è meglio, 1 o 3? Il tie breaker rilevante è: un metodo applicabile solo nella sua forma espansa è sempre peggiore di un metodo applicabile nella sua forma normale. Quindi 1 è peggio di 3. Quindi 1 non può essere il vincitore.

Quale è meglio, 2 o 3? Il tie breaker rilevante è che un metodo generico è sempre peggio di un metodo non generico. 2 è peggio di 3. Quindi 2 non può essere il vincitore.

Per essere scelto tra una serie di candidati multipli applicabili, un candidato deve essere (1) imbattuto, (2) battere almeno un altro candidato e (3) essere il candidato unico che ha le prime due proprietà. Il candidato tre non viene battuto da nessun altro candidato e batte almeno un altro candidato; è l'unico candidato con questa proprietà. Pertanto, il candidato tre è il miglior candidato unico . Dovrebbe vincere.

Non solo il compilatore C # 4 sta sbagliando, come si nota correttamente, sta segnalando un bizzarro messaggio di errore. Il fatto che il compilatore stia facendo l'analisi della risoluzione di sovraccarico sbagliata è un po 'sorprendente. Che stia ottenendo il messaggio di errore sbagliato non sorprende affatto; l'euristico errore "metodo ambiguo" seleziona fondamentalmente due metodi dal set candidato se non è possibile determinare un metodo migliore. Non è molto bravo a trovare la "vera" ambiguità, se in realtà ce n'è una.

Si potrebbe ragionevolmente chiedere perché sia ​​così. È piuttosto difficile trovare due metodi "ambiguamente ambigui" perché la relazione "betterness" è intransitiva . È possibile arrivare a situazioni in cui il candidato 1 è migliore di 2, 2 è meglio di 3 e 3 è migliore di 1. In tali situazioni non possiamo fare di meglio che sceglierne due come "gli ambigui".

Mi piacerebbe migliorare questa euristica per Roslyn, ma è una priorità bassa.

(Esercizio al lettore: "Definire un algoritmo lineare per identificare il miglior membro unico di un insieme di n elementi in cui la relazione di battistrada è intransitiva" è stata una delle domande che mi sono state poste il giorno in cui ho intervistato per questa squadra. un algoritmo molto difficile, provalo.)

Uno dei motivi per cui abbiamo deciso di aggiungere argomenti facoltativi a C # per così tanto tempo è stato il numero di complesse situazioni ambigue che introduce nell'algoritmo di risoluzione del sovraccarico; a quanto pare non abbiamo capito bene.

Se desideri inserire un problema di connessione per tracciarlo, sentiti libero. Se vuoi che venga portato alla nostra attenzione, consideralo fatto. Seguirò i test l'anno prossimo.

Grazie per avermelo fatto notare. Ci scusiamo per l'errore.


Il tipo di restituzione del metodo signatue è Task se non esiste un tipo restituito, o Task<T> se esiste un tipo restituito.

Però, non sono sicuro al 100% se puoi avere agnelli asinconici come quello.

Nel metodo che sta consumando l'attività, si dovrebbe "attendere" l'attività o utilizzare le proprietà e i metodi su Attività per ottenere il risultato.







c# overload-resolution