Perché l'aggiunta di un metodo aggiungerebbe una chiamata ambigua, se non fosse coinvolta nell'ambiguità


112

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 così:

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

Scrive Normal Winnersulla 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 che già esistevano (params string[])e <string>(string)! Chiaramente nessuno dei due metodi coinvolti nell'ambiguità è il metodo appena aggiunto, perché il primo è un parametro e il secondo è un generico.

è un insetto? Quale parte delle specifiche dice che dovrebbe essere così?


2
Non credo si 'Overloaded.ComplexOverloadResolution(string)'riferisca al <string>(string)metodo; Penso che si riferisca al (string, object)metodo senza oggetto fornito.
phoog

1
@phoog Oh, quei dati sono stati tagliati da StackOverflow perché è un tag, ma il messaggio di errore ha il designatore del modello. Lo aggiungo di nuovo.
McKay

mi hai beccato! Ho citato le sezioni pertinenti delle specifiche nella mia risposta, ma non ho passato l'ultima mezz'ora a leggerle e comprenderle!
phoog

@phoog, guardando attraverso quelle parti della specifica, non vedo nulla sull'introduzione di ambiguità in metodi diversi da se stesso e un altro metodo, non altri due metodi.
McKay

Mi è venuto in mente che si tratta solo di sasso-carta-forbici : qualsiasi insieme di due valori distinti ha un vincitore, ma l'insieme completo di tre valori no.
phoog

Risposte:


107

è un insetto?

Sì.

Congratulazioni, hai trovato un bug nella risoluzione dell'overload. 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 esaminare e risolvere prima del rilascio 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) 

Il candidato zero è ovviamente inapplicabile perché stringnon convertibile in string[]. Ne rimangono tre.

Dei tre, dobbiamo determinare un metodo migliore unico. Lo facciamo effettuando confronti a coppie dei tre candidati rimanenti. Ci sono tre di queste coppie. Tutti hanno elenchi di parametri identici una volta rimossi i parametri opzionali omessi, il che significa che dobbiamo passare al round di spareggio avanzato descritto nella sezione 7.5.3.2 della specifica.

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

Quale è meglio, 1 o 3? Lo spareggio rilevante è: un metodo applicabile solo nella sua forma estesa è 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 tiebreaker rilevante è che un metodo generico è sempre peggiore di un metodo non generico. 2 è peggio di 3. Quindi 2 non può essere il vincitore.

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

Non solo il compilatore C # 4 sta sbagliando, come si nota correttamente che sta segnalando un bizzarro messaggio di errore. Che il compilatore abbia sbagliato l'analisi della risoluzione del sovraccarico è un po 'sorprendente. Che stia ottenendo il messaggio di errore sbagliato non è assolutamente sorprendente; l'euristica dell'errore del "metodo ambiguo" fondamentalmente sceglie due metodi qualsiasi dall'insieme candidato se non è possibile determinare un metodo migliore. Non è molto bravo a trovare la "vera" ambiguità, se in effetti ce n'è una.

Ci si potrebbe ragionevolmente chiedere perché sia ​​così. È abbastanza complicato trovare due metodi che siano "inequivocabilmente ambigui" perché la relazione "migliore" è intransitiva . È possibile trovare situazioni in cui il candidato 1 è migliore di 2, 2 è migliore di 3 e 3 è migliore di 1. In tali situazioni non possiamo fare di meglio che sceglierne due come "ambigui".

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

(Esercizio per il lettore: "Elaborare un algoritmo tempo lineare per identificare il miglior membro unico di un insieme di n elementi in cui la relazione migliore è intransitiva" è stata una delle domande che mi sono state poste il giorno in cui ho intervistato questo team. Non è un algoritmo molto difficile; provalo.)

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

Se desideri inserire un problema di Connect per monitorarlo, sentiti libero. Se vuoi solo che venga portato alla nostra attenzione, consideralo fatto. Seguirò i test il prossimo anno.

Grazie per avermelo fatto notare. Mi scuso per l'errore.


1
Grazie per la risposta. hai detto "1 è peggio di 2", ma sceglie il metodo 1 se ho solo il metodo 1 e 2?
McKay

@ McKay: Whoops, hai ragione, l'ho affermato al contrario. Sistemerò il testo.
Eric Lippert

1
È imbarazzante leggere la frase "il resto dell'anno" considerando che non ne resta nemmeno mezza settimana :)
BoltClock

2
@BoltClock infatti, l'affermazione "in partenza per il resto dell'anno" implica un giorno libero.
phoog

1
Credo di si. Ho letto "3) sii l'unico candidato che ha le prime due proprietà" come "sii l'unico candidato che (è imbattuto e batte almeno un altro candidato)" . Ma il tuo ultimo commento mi fa pensare "(sii l'unico candidato che è imbattuto) e batte almeno un altro candidato" . L'inglese potrebbe davvero usare simboli di raggruppamento. Se quest'ultimo è vero, lo capisco di nuovo.
default.kramer

5

Quale parte delle specifiche dice che dovrebbe essere così?

Sezione 7.5.3 (risoluzione del 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 in parte dice "i parametri opzionali senza argomenti corrispondenti vengono rimossi dall'elenco 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 queste parti della specifica abbastanza a fondo da sapere quali parti della specifica controllano questo comportamento, figuriamoci per giudicare se è conforme.


Ma questo non spiega perché l'aggiunta di un membro causerebbe un'ambiguità tra due metodi già esistenti.
McKay

@ McKay abbastanza giusto (vedi modifica). Non ci resta che aspettare che Eric Lippert ci dica se si tratta di un comportamento corretto: ->
phoog

1
Quelle sono le parti giuste della specifica, d'accordo. Il problema è che dicono che non dovrebbe essere così!
Eric Lippert

3

puoi evitare questa ambiguità cambiando il nome del primo parametro in alcuni metodi e specificando il parametro che vuoi assegnare

come questo :

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");
    }
}

Oh, so che questo codice è sbagliato e ci sono diversi modi per aggirarlo, la domanda era "perché il compilatore si comporta in questo modo?"
McKay

1

Se rimuovi il paramsdal tuo primo metodo, ciò non accadrebbe. Il primo e il terzo metodo hanno entrambi chiamate valide ComplexOverloadResolution(string), ma se il primo metodo è public void ComplexOverloadResolution(string[] something)presente non ci saranno ambiguità.

Fornire un valore per un parametro lo object somethingElse = nullrende un parametro facoltativo e quindi non deve essere specificato quando si chiama tale overload.

Modifica: il compilatore sta facendo cose pazze qui. Se sposti il ​​terzo metodo nel codice dopo il primo, verrà segnalato correttamente. Quindi sembra che stia prendendo i primi due sovraccarichi e segnalali, senza controllare quello corretto.

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

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


Ma questo non spiega perché l'aggiunta di un membro causerebbe un'ambiguità tra due metodi già esistenti.
McKay

L'ambiguità si verifica tra il primo e il terzo metodo, ma il motivo per cui il compilatore segnala gli altri due è al di là di me.
Tomislav Markovski

Ma se rimuovo il secondo metodo, non ho ambiguità, chiama con successo il terzo metodo. Quindi non sembra che il compilatore abbia un'ambiguità tra il primo e il terzo metodo.
McKay

Vedi la mia modifica. Compilatori pazzi.
Tomislav Markovski

In realtà, qualsiasi combinazione di due soli metodi non produce ambiguità. Questo è molto strano. Edit2.
Tomislav Markovski

1
  1. Se scrivi

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

    o semplicemente scrivi

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

    finirà nello stesso metodo , in metodo

    public void ComplexOverloadResolution(params string[] something

    Questa è la causa della paramsparola chiave che ne fa la migliore corrispondenza anche nel caso in cui non sia specificato alcun parametro

  2. Se provi ad aggiungere il tuo nuovo metodo come questo

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

    Compilerà e chiamerà perfettamente questo metodo poiché è una corrispondenza perfetta per la tua chiamata con un stringparametro. Molto più forte allora params string[] something.

  3. Dichiari il secondo metodo come hai fatto

    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 tutti ora sulla tua chiamata

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

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

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

Per prima cosa, scrivi due casi di chiamata che sono gli stessi. Quindi ovviamente seguirà lo stesso metodo o intendevi scrivere qualcosa di diverso?
McKay

Ma ancora una volta, se capisco correttamente il resto della tua risposta, non stai leggendo ciò che il compilatore dice è la confusione, vale a dire tra il primo e il secondo metodo, non il nuovo terzo che ho appena aggiunto.
McKay

ah, grazie. Ma questo lascia ancora il problema che ho menzionato nel mio secondo commento al tuo post.
McKay

Per essere più chiari, affermi: "Compilatore, salta in totale confusione tra il primo metodo e questo, ne ha appena aggiunto uno". Ma non lo è. Sta saltando in confusione sugli altri due metodi. Il metodo params e il metodo generico.
McKay

@ McKay: beh, è ​​un salto di confusione avere un dato statedi 3 funzioni, non una o una coppia di esse, per essere precisi. Infatti, è sufficiente commentare qualcuno di loro, per risolvere un problema. La migliore corrispondenza tra le funzioni disponibili è quella con params, la seconda è quella con genericsparametro, quando aggiungiamo la terza si crea confusione in una setdelle funzioni. Penso che, molto probabilmente, non sia un messaggio di errore chiaro prodotto dal compilatore.
Tigran
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.