¿Por qué agregar un método agregaría una llamada ambigua, si no estaría involucrado en la ambigüedad?

112

Tengo esta clase

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

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

Si lo llamo así:

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

Escribe Normal Winneren la consola.

Pero, si agrego otro método:

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

Obtuve el siguiente error:

La llamada es ambigua entre los siguientes métodos o propiedades:> ' Overloaded.ComplexOverloadResolution(params string[])' y ' Overloaded.ComplexOverloadResolution<string>(string)'

Puedo entender que agregar un método podría introducir una ambigüedad en la llamada, ¡pero es una ambigüedad entre los dos métodos que ya existían (params string[])y <string>(string)! Claramente, ninguno de los dos métodos involucrados en la ambigüedad es el método recién agregado, porque el primero es params y el segundo es genérico.

¿Es esto un error? ¿Qué parte de la especificación dice que este debería ser el caso?

McKay
fuente
2
No creo que se 'Overloaded.ComplexOverloadResolution(string)'refiera al <string>(string)método; Creo que se refiere al (string, object)método sin objeto proporcionado.
phoog
1
@phoog Oh, StackOverflow cortó esos datos porque es una etiqueta, pero el mensaje de error tiene el designador de plantilla. Lo voy a agregar de nuevo.
McKay
¡me has pillado! ¡Cité las secciones relevantes de la especificación en mi respuesta, pero no he pasado la última media hora leyéndolas y entendiéndolas!
phoog
@phoog, mirando a través de esas partes de la especificación, no veo nada acerca de introducir ambigüedad en métodos que no sean él mismo y otro método, no otros dos métodos.
McKay
Se me ocurrió que esto es solo piedra-papel-tijera : cualquier conjunto de dos valores distintos tiene un ganador, pero el conjunto completo de tres valores no.
phoog

Respuestas:

107

¿Es esto un error?

Si.

Felicitaciones, ha encontrado un error en la resolución de sobrecarga. El error se reproduce en C # 4 y 5; no se reproduce en la versión "Roslyn" del analizador semántico. He informado al equipo de prueba de C # 5 y espero que podamos investigar y resolver esto antes del lanzamiento final. (Como siempre, sin promesas).

Sigue un análisis correcto. Los candidatos son:

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

El candidato cero es obviamente inaplicable porque stringno es convertible a string[]. Eso deja tres.

De los tres, debemos determinar el mejor método único. Lo hacemos haciendo comparaciones por pares de los tres candidatos restantes. Hay tres pares de este tipo. Todos ellos tienen listas de parámetros idénticas una vez que eliminamos los parámetros opcionales omitidos, lo que significa que tenemos que pasar a la ronda avanzada de desempate descrita en la sección 7.5.3.2 de la especificación.

¿Qué es mejor, 1 o 2? El desempate relevante es que un método genérico siempre es peor que un método no genérico. 2 es peor que 1. Así que 2 no puede ser el ganador.

¿Cuál es mejor, 1 o 3? El desempate relevante es: un método aplicable solo en su forma expandida es siempre peor que un método aplicable en su forma normal. Por tanto, 1 es peor que 3. Por tanto, 1 no puede ser el ganador.

¿Qué es mejor, 2 o 3? El desempate relevante es que un método genérico siempre es peor que un método no genérico. 2 es peor que 3. Así que 2 no puede ser el ganador.

Para ser elegido de un conjunto de múltiples candidatos aplicables, un candidato debe estar (1) invicto, (2) vencer al menos a otro candidato y (3) ser el candidato único que tiene las dos primeras propiedades. El candidato tres no es derrotado por ningún otro candidato y gana al menos a otro candidato; es el único candidato con esta propiedad. Por lo tanto, el candidato tres es el mejor candidato único . Debería ganar.

El compilador de C # 4 no solo se equivoca, sino que también notó correctamente que está informando un extraño mensaje de error. Que el compilador esté haciendo mal el análisis de resolución de sobrecarga es un poco sorprendente. Que el mensaje de error sea incorrecto no es ninguna sorpresa; la heurística de error del "método ambiguo" básicamente elige dos métodos del conjunto de candidatos si no se puede determinar el mejor método. No es muy bueno para encontrar la ambigüedad "real", si es que la hay.

Uno podría preguntarse razonablemente por qué es así. Es bastante complicado encontrar dos métodos que sean "inequívocamente ambiguos" porque la relación de "mejoría" es intransitiva . Es posible llegar a situaciones en las que el candidato 1 es mejor que 2, 2 es mejor que 3 y 3 es mejor que 1. En tales situaciones, no podemos hacer nada mejor que elegir dos de ellos como "los ambiguos".

Me gustaría mejorar esta heurística para Roslyn pero es una prioridad baja.

(Ejercicio para el lector: "Diseñar un algoritmo de tiempo lineal para identificar el mejor miembro único de un conjunto de n elementos donde la relación de mejora es intransitiva" fue una de las preguntas que me hicieron el día que me entrevisté para este equipo. No es un algoritmo muy difícil; pruébalo.)

Una de las razones por las que rechazamos la adición de argumentos opcionales a C # durante tanto tiempo fue la cantidad de situaciones ambiguas complejas que introduce en el algoritmo de resolución de sobrecarga; aparentemente no lo hicimos bien.

Si desea ingresar un problema de Connect para rastrearlo, no dude. Si solo quiere que nos lo notifique, considérelo hecho. Seguiré con las pruebas el próximo año.

Gracias por informarme sobre esto. Disculpas por el error.

Eric Lippert
fuente
1
Gracias por la respuesta. dijiste "1 es peor que 2", pero elige el método 1 si solo tengo el método 1 y 2
McKay
@McKay: Vaya, tienes razón, lo dije al revés. Arreglaré el texto.
Eric Lippert
1
Se siente incómodo leer la frase "el resto del año" considerando que no queda ni media semana :)
BoltClock
2
@BoltClock de hecho, la declaración "dejar para el resto del año" implica un día libre.
phoog
1
Creo que sí. Leí "3) ser el candidato único que tiene las dos primeras propiedades" como "ser el único candidato que (está invicto y supera al menos a otro candidato)" . Pero tu comentario más reciente me hace pensar "(sé el único candidato que está invicto) y supera al menos a otro candidato" . El inglés realmente podría usar símbolos de agrupación. Si esto último es cierto, lo entiendo de nuevo.
default.kramer
5

¿Qué parte de la especificación dice que este debería ser el caso?

Sección 7.5.3 (resolución de sobrecarga), junto con las secciones 7.4 (búsqueda de miembros) y 7.5.2 (inferencia de tipos).

Nótese especialmente la sección 7.5.3.2 (mejor miembro de función), que dice en parte "los parámetros opcionales sin argumentos correspondientes se eliminan de la lista de parámetros" y "Si M (p) es un método no genérico y M (q) es un método genérico, entonces M (p) es mejor que M (q) ".

Sin embargo, no entiendo estas partes de la especificación lo suficientemente a fondo como para saber qué partes de la especificación controlan este comportamiento, y mucho menos para juzgar si es compatible.

phoog
fuente
Pero eso no explica por qué agregar un miembro causaría una ambigüedad entre dos métodos que ya existían.
McKay
@McKay bastante justo (ver editar). Solo tendremos que esperar a que Eric Lippert nos diga si este es el comportamiento correcto: ->
phoog
1
Esas son las partes correctas de la especificación. ¡El problema es que dicen que este no debería ser el caso!
Eric Lippert
3

puede evitar esta ambigüedad cambiando el nombre del primer parámetro en algunos métodos y especificando el parámetro que desea asignar

Me gusta esto :

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
fuente
Oh, sé que este código es malo, y hay varias formas de solucionarlo, la pregunta era "¿por qué el compilador se comporta de esta manera?"
McKay
1

Si elimina el paramsde su primer método, esto no sucederá. ComplexOverloadResolution(string)Su primer y tercer método tienen llamadas válidas , pero si su primer método es, public void ComplexOverloadResolution(string[] something)no habrá ambigüedad.

Proporcionar valor para un parámetro lo object somethingElse = nullconvierte en un parámetro opcional y, por lo tanto, no tiene que especificarse al llamar a esa sobrecarga.

Editar: El compilador está haciendo cosas locas aquí. Si mueve su tercer método en código después del primero, informará correctamente. Así que parece que está tomando las dos primeras sobrecargas y las reporta, sin verificar cuál es la correcta.

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

Edit2: Nuevo hallazgo. Eliminar cualquier método de los tres anteriores no producirá ambigüedad entre los dos. Entonces, parece que el conflicto solo aparece si hay tres métodos presentes, independientemente del orden.

Tomislav Markovski
fuente
Pero eso no explica por qué agregar un miembro causaría una ambigüedad entre dos métodos que ya existían.
McKay
La ambigüedad ocurre entre su primer y tercer método, pero ¿por qué el compilador informa de los otros dos?
Tomislav Markovski
Pero si elimino el segundo método, no tengo ambigüedad, llama con éxito al tercer método. Por tanto, no parece que el compilador tenga ambigüedad entre el primer y el tercer método.
McKay
Ver mi Editar. Compiladores locos.
Tomislav Markovski
En realidad, cualquier combinación de solo dos métodos no produce ambigüedad. Esto es muy extraño. Editar 2.
Tomislav Markovski
1
  1. Si tú escribes

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

    o simplemente escribe

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

    terminará en el mismo método , en el método

    public void ComplexOverloadResolution(params string[] something

    Esta es la paramspalabra clave de la causa que hace que coincida mejor también para el caso en que no se haya especificado ningún parámetro

  2. Si intenta agregar su nuevo método como este

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

    Compilará y llamará perfectamente a este método, ya que es una combinación perfecta para su llamada con un stringparámetro. Mucho más fuerte entonces params string[] something.

  3. Declaras el segundo método como lo hiciste

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

    Compilador, salta en total confusión entre el primer método y este, que acaba de agregar uno. Porque no sabe qué función debería tener ahora en tu llamada

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

    De hecho, si elimina el parámetro de cadena de la llamada, como el siguiente código, todo se compila correctamente y funciona como antes

    var blah = new Overloaded();
    blah.ComplexOverloadResolution(); // will be ComplexOverloadResolution(params string[] something) function called here, like a best match.
Tigran
fuente
Primero, escribe dos casos de llamada que son iguales. Entonces, por supuesto, usará el mismo método, ¿o pretendías escribir algo diferente?
McKay
Pero nuevamente, si entiendo el resto de su respuesta correctamente, no está leyendo lo que el compilador dice que es la confusión, es decir, entre el primer y segundo método, no el nuevo tercero que acabo de agregar.
McKay
Ah gracias. Pero eso aún deja el problema que menciono en mi segundo comentario a tu publicación.
McKay
Para ser más claro, dice "Compilador, salta en total confusión entre el primer método y este, que acaba de agregar uno". Pero no lo es. Está saltando en confusión a los otros dos métodos. El método params y el método genérico.
McKay
@McKay: bueno, es un salto de confusión tener state3 funciones, no una o dos, para ser precisos. De hecho, basta con comentar alguno de ellos para resolver un problema. La mejor coincidencia entre las funciones disponibles es aquella con params, la segunda es aquella con genericsparámetro, cuando agregamos la tercera crea una confusión en una setde las funciones. Creo que lo más probable es que no sea un mensaje de error claro producido por el compilador.
Tigran