2011-01-01 27 views
19

Este me tiene desconcertado, así que pensé en preguntar aquí con la esperanza de que un gurú de C# pueda explicarlo.¿Por qué es Func <T> ambiguo con Func <IEnumerable <T>>?

¿Por qué este código genera un error?

class Program 
{ 
    static void Main(string[] args) 
    { 
     Foo(X); // the error is on this line 
    } 

    static String X() { return "Test"; } 

    static void Foo(Func<IEnumerable<String>> x) { } 
    static void Foo(Func<String> x) { } 
} 

El error en cuestión:

Error 
    1 
    The call is ambiguous between the following methods or properties: 
'ConsoleApplication1.Program.Foo(System.Func<System.Collections.Generic.IEnumerable<string>>)' and 'ConsoleApplication1.Program.Foo(System.Func<string>)' 
    C:\Users\mabster\AppData\Local\Temporary Projects\ConsoleApplication1\Program.cs 
    12 
    13 
    ConsoleApplication1 

No importa qué tipo utilizo - si reemplaza las declaraciones "string" con "int" en ese código que obtendrá la misma tipo de error. Es como si el compilador no pudiera distinguir entre Func<T> y Func<IEnumerable<T>>.

¿Alguien puede arrojar algo de luz sobre esto?

Respuesta

24

Bien, este es el trato.

La versión corta:

  • El error de ambigüedad es, extrañamente suficiente, correcta.
  • El compilador C# 4 también produce un error espurio después del error de ambigüedad correcto. Eso parece ser un error en el compilador.

La versión larga:

Tenemos un problema de resolución de sobrecarga. La resolución de sobrecarga está muy bien especificada.

Paso uno: determine el conjunto candidato. Eso es fácil. Los candidatos son Foo(Func<IEnumerable<String>>) y Foo(Func<String>).

Paso dos: determine qué miembros del conjunto de candidatos son aplicable. Un miembro aplicable tiene todos los argumentos convertibles a cada tipo de parámetro.

¿Es Foo(Func<IEnumerable<String>>) aplicable? Bueno, ¿es X convertible a Func<IEnumerable<String>?

Consultamos la sección 6.6 de la especificación. Esta parte de la especificación es lo que los diseñadores de idiomas llamamos "realmente extraño". Básicamente, dice que puede existir una conversión, pero usar esa conversión es un error. (Hay buenas razones por las que tenemos esta situación extraña, que principalmente tiene que ver con evitar cambios futuros y evitar situaciones de "gallina y huevo", pero en su caso estamos teniendo un comportamiento desafortunado)

Básicamente , la regla aquí es que existe una conversión de X a un tipo de delegado sin parámetros si la resolución de sobrecarga en una llamada del formulario X() tuviera éxito. Claramente, tal llamada sería, y por lo tanto existe una conversión. En realidad, usando esa conversión es un error porque los tipos de devolución no coinciden, pero la resolución de sobrecarga siempre ignora los tipos de devolución.

Entonces, existe una conversión de X a Func<IEnumerable<String>, y por lo tanto esa sobrecarga es un candidato aplicable.

Obviamente por la misma razón, la otra sobrecarga también es un candidato aplicable.

Paso tres: ahora tenemos dos candidatos aplicables. Cuál es mejor"?

El que es "mejor" es el que tiene el tipo más específico. Si tiene dos candidatos aplicables, M(Animal) y M(Giraffe), elegimos la versión de Jirafa porque una jirafa es más específica que un animal. Sabemos que la jirafa es más específica porque cada jirafa es un animal, pero no todos los animales son jirafas.

Pero en su caso ninguno es más específico que el otro. No hay conversión entre los dos tipos de Func.

Por lo tanto, tampoco es mejor, por lo que la resolución de sobrecarga informa un error.

El compilador C# 4 tiene entonces lo que parece ser un error, donde su modo de recuperación de error selecciona uno de los candidatos de todos modos e informa otro error. No me queda claro por qué está sucediendo eso. Básicamente, está diciendo que la recuperación de errores está eligiendo la sobrecarga IEnumerable, y luego observa que la conversión del grupo de métodos produce un resultado insostenible; a saber, esa cadena no es compatible con IEnumerable<String>.

Toda la situación es desafortunada; podría haber sido mejor decir que no hay conversión de método de grupo a delegado si los tipos de retorno no coinciden. (O bien, que una conversión que produce un error siempre es peor que una conversión que no lo hace.) Sin embargo, estamos bloqueados ahora.

Un hecho interesante: las reglas de conversión para lambdas do tienen en cuenta los tipos de devolución. Si dice Foo(()=>X()), entonces haremos lo correcto. El hecho de que las lambdas y los grupos de métodos tengan diferentes reglas de convertibilidad es bastante desafortunado.

Por lo tanto, resumiendo, el compilador es en realidad una implementación correcta de la especificación en este caso, y este escenario particular es una consecuencia involuntaria de algunas opciones de especificaciones posiblemente desafortunadas.

+0

"podría haber sido mejor decir que no hay una conversión de grupo de métodos a delegar si los tipos de devolución no coinciden": me parece mejor ... No creo que cambiar esa regla ser un cambio radical, ¿verdad? Es decir, que yo sepa, no hay forma de producir un código de trabajo que use dicha conversión (tal vez no lo intenté lo suficiente, pero siempre recibo "XXX tiene el tipo de devolución incorrecto") –

+4

@Thomas : Convertir algo que solía ser un error en un caso de éxito puede hacer que un problema de resolución de sobrecarga tenga una solución para tener dos soluciones ambiguas, ¡lo que ahora está convirtiendo un caso de éxito en un caso de error! Es sorprendente cuántas cosas están alterando técnicamente los cambios, pero en este caso creo que valdría la pena. Lo discutiré con Mads. –

+1

Cita: "la resolución de sobrecarga siempre ignora los tipos de devolución" y "las lambdas y los grupos de métodos tienen diferentes reglas de convertibilidad" - ¡bastante desafortunado, pero creo que esta es una maravillosa explicación! –

7

Su código requiere que ocurra "magia" dos veces, una para convertir del grupo de métodos nombrado a un delegado, y una para realizar una resolución de sobrecarga.

A pesar de que solo tiene un método denominado X, las reglas del compilador se crean para el caso cuando hay varias.

Además, como los delegados no tienen que coincidir exactamente con la firma del método, la complejidad aumenta aún más. Además de eso, cualquier método dado se puede convertir en un número ilimitado de tipos de delegados diferentes con firmas idénticas.

Su caso particular parece lo suficientemente sencillo, pero el caso general es muy difícil, por lo que el lenguaje no lo permite.

Si haces parte del trabajo a mano, podrás resolver el problema. p.ej.

Func<string> d = X; 
Foo(d); 

compila bien.

Cuestiones relacionadas