2011-03-05 10 views
8

La siguiente llamada a la Enumerable.Select método sobrecargado:El argumento de grupo de métodos sobrecargado confunde la resolución de sobrecarga?

var itemOnlyOneTuples = "test".Select<char, Tuple<char>>(Tuple.Create); 

falla con un error de ambigüedad (espacios de nombres eliminadas para mayor claridad):

The call is ambiguous between the following methods or properties: 
'Enumerable.Select<char,Tuple<char>> 
      (IEnumerable<char>,Func<char,Tuple<char>>)' 
and 
'Enumerable.Select<char,Tuple<char>> 
      (IEnumerable<char>, Func<char,int,Tuple<char>>)' 

ciertamente puedo entender por qué no Especificación del tipo de argumentos explícitamente resultaría en una ambigüedad (ambas sobrecargas se aplicarían), pero no veo una después de hacerlo.

Parece lo suficientemente claro para mí que la intención es llamar a la primera sobrecarga, con el argumento de grupo de métodos resuelto a Tuple.Create<char>(char). La segunda sobrecarga no debería aplicarse porque ninguna de las sobrecargas Tuple.Create se puede convertir al tipo esperado Func<char,int,Tuple<char>>. Estoy adivinando el compilador se confunde por Tuple.Create<char, int>(char, int), pero su tipo de retorno es incorrecto: devuelve una tupla de dos, y por lo tanto no es convertible al tipo relevante Func.

Por cierto, algo de lo siguiente hace que el compilador feliz:

  1. Especificación de un tipo de argumento para el argumento método de grupo: Tuple.Create<char> (Tal vez esto es en realidad un problema de tipo de inferencia?).
  2. convirtiendo el argumento en una expresión lambda en lugar de un grupo de métodos: x => Tuple.Create(x). (Juega bien con la inferencia de tipos en la llamada Select).

Como era de esperar, tratando de llamar a la otra sobrecarga de Select de esta manera también falla:

var itemIndexTwoTuples = "test".Select<char, Tuple<char, int>>(Tuple.Create); 

¿Cuál es el problema exacto aquí?

Respuesta

20

En primer lugar, quiero señalar que este es un duplicado de:

Why is Func<T> ambiguous with Func<IEnumerable<T>>?

¿Cuál es el problema exacto aquí?

Thomas's guess is esencialmente correct. Aquí están los detalles exactos.

Vamos a ir paso a paso.Tenemos una invocación:

"test".Select<char, Tuple<char>>(Tuple.Create); 

La resolución de sobrecarga debe determinar el significado de la llamada a Seleccionar. No hay un método "Seleccionar" en la cadena o cualquier clase base de cadena, por lo que este debe ser un método de extensión.

Hay una serie de posibles métodos de extensión para el conjunto de candidatos porque la cadena es convertible a IEnumerable<char> y presumiblemente hay un using System.Linq; allí en alguna parte. Hay muchos métodos de extensión que coinciden con el patrón "Seleccionar, aridad genérica dos, toma un IEnumerable<char> como primer argumento cuando se construye con los argumentos de tipo de método dados".

En particular, dos de los candidatos son:

Enumerable.Select<char,Tuple<char>>(IEnumerable<char>,Func<char,Tuple<char>>) 
Enumerable.Select<char,Tuple<char>>(IEnumerable<char>,Func<char,int,Tuple<char>>) 

Ahora, la primera pregunta que nos enfrentamos es que los candidatos son aplicables ? Es decir, ¿hay una conversión implícita de cada argumento proporcionado al tipo de parámetro formal correspondiente?

Una excelente pregunta. Claramente, el primer argumento será el "receptor", una cadena, y será implícitamente convertible a IEnumerable<char>. La pregunta ahora es si el segundo argumento, el grupo de métodos "Tuple.Create", es implícitamente convertible a los tipos de parámetros formales Func<char,Tuple<char>> y Func<char,int, Tuple<char>>.

¿Cuándo es un grupo de métodos convertible a un tipo de delegado determinado? Un grupo de métodos es convertible a un tipo de delegado cuando la resolución de sobrecarga hubiera tenido éxito dados argumentos del mismo tipo que los tipos de parámetros formales del delegado.

Es decir, M es convertible a Func<A, R> si la resolución de sobrecarga en una llamada del formulario M(someA) hubiera tenido éxito, dada una expresión 'someA' del tipo 'A'.

¿Habría tenido éxito la resolución de sobrecarga en una llamada al Tuple.Create(someChar)? Sí; la resolución de sobrecarga habría elegido Tuple.Create<char>(char).

¿Habría tenido éxito la resolución de sobrecarga en una llamada al Tuple.Create(someChar, someInt)? Sí, la resolución de sobrecarga habría elegido Tuple.Create<char,int>(char, int).

Dado que en ambos casos la resolución de sobrecarga hubiera tenido éxito, el grupo de métodos es convertible a ambos tipos de delegados. El hecho de que el tipo de devolución de uno de los métodos no coincida con el tipo de devolución del delegado es irrelevante; la resolución de sobrecarga no funciona o falla según el análisis del tipo de devolución.

Podría decirse razonablemente que la convertibilidad de los grupos de métodos para delegar los tipos debe tener éxito o no basarse en el análisis del tipo de devolución, pero no es así como se especifica el idioma; el idioma se especifica para usar la resolución de sobrecarga como la prueba para la conversión del grupo de métodos, y creo que es una opción razonable.

Por lo tanto, tenemos dos candidatos aplicables. ¿Hay alguna manera de que podamos decidir cuál es mejor que el otro?La especificación indica que la conversión a tipo más específico es mejor; si tiene

void M(string s) {} 
void M(object o) {} 
... 
M(null); 

entonces la resolución de sobrecarga elige la versión de cadena porque la cadena es más específica que el objeto. ¿Es uno de esos tipos de delegados más específico que el otro? No. Ninguno es más específico que el otro. (Esta es una simplificación de las reglas de conversión mejorada, en realidad hay muchos desempates, pero ninguno de ellos se aplica aquí.)

Por lo tanto, no hay ninguna base para preferir uno sobre el otro.

Nuevamente, uno podría decir razonablemente que, hay una base, es decir, que una de esas conversiones produciría un error de desajuste del tipo de devolución del delegado y una de ellas no. De nuevo, sin embargo, el lenguaje se especifica para razonar sobre la mejoría al considerar las relaciones entre los tipos de parámetros formales , y no sobre si la conversión que ha elegido dará como resultado eventualmente un error.

Dado que no hay ninguna base sobre la cual preferir uno sobre el otro, este es un error de ambigüedad.

Es fácil construir errores de ambigüedad similares. Por ejemplo:

void M(Func<int, int> f){} 
void M(Expression<Func<int, int>> ex) {} 
... 
M(x=>Q(++x)); 

Eso es ambiguo. Aunque es ilegal tener un ++ dentro de un árbol de expresiones, la lógica de convertibilidad no considera si el cuerpo de un lambda tiene algo dentro de él que sería ilegal en un árbol de expresiones. La lógica de conversión simplemente se asegura de que los tipos revisen, y lo hacen. Dado eso, no hay ninguna razón para preferir una de las M sobre la otra, así que esto es una ambigüedad.

nota de que

"test".Select<char, Tuple<char>>(Tuple.Create<char>); 

tiene éxito. Ahora sabes por qué. resolución de sobrecarga debe determinar si

Tuple.Create<char>(someChar) 

o

Tuple.Create<char>(someChar, someInt) 

tendría éxito. Dado que el primero lo hace y el segundo no, el segundo candidato es inaplicable y eliminado, y por lo tanto no está cerca para volverse ambiguo.

También tenga en cuenta que

"test".Select<char, Tuple<char>>(x=>Tuple.Create(x)); 

es inequívoca. Las conversiones de Lambda do tienen en cuenta la compatibilidad del tipo de expresión devuelta con el tipo de devolución del delegado de destino. Es desafortunado que los grupos de métodos y las expresiones lambda usen dos algoritmos sutilmente diferentes para determinar la convertibilidad, pero ahora estamos atascados. Recuerde, las conversiones de grupos de métodos han estado en el lenguaje mucho más tiempo que las conversiones de lambda; si se hubieran agregado al mismo tiempo, me imagino que sus reglas se habrían hecho consistentes.

+4

Eric, la afirmación de que * "estamos atrapados con [dos algoritmos sutilmente diferentes]" * la convertibilidad sugiere que hay una la compatibilidad de compatibilidad con versiones anteriores en juego aquí, ¿sí? Presumiblemente, permitir conversiones de grupos de métodos para utilizar el análisis del tipo de retorno para la resolución crearía situaciones en las que el código existente daría como resultado una opción de sobrecarga diferente. Sin embargo, no puedo construir un escenario donde esto realmente suceda, ¿existe realmente tal caso? ¿O se trata más bien de equilibrar riesgo/recompensa para un caso de uso de bajo valor? – LBushkin

+0

Realmente aprecio los detalles en esta respuesta; Creo que tengo un poco mejor comprensión de esto ahora. De hecho, casi esperaba que mi código funcionara porque tenía la impresión de que al tipo de retorno de los grupos de métodos se le daba más "atención" en C# 4, pero presumiblemente eso solo se aplica a la inferencia de tipos. De todos modos, para mí, la resolución de sobrecarga y la inferencia de tipo tienen un poco de magia negra sobre ellos; mi intuición me decepciona bastante a menudo. – Ani

+0

En otra nota, me resulta un poco desconcertante que C# tiende a separar consistentemente los siguientes dos aspectos con tanta fuerza: a) lo que podría haber significado el usuario b) es lo que el usuario podría haber querido decir legal. ¿Está mal querer que 'b)' juegue un papel más importante en 'a)'? – Ani

5

Supongo que el compilador está confundido por Tuple.Create<char, int>(char, int), pero su tipo de retorno es incorrecto: devuelve una tupla de dos.

El tipo de devolución no forma parte de la firma del método, por lo que no se considera durante la resolución de sobrecarga; solo se ha verificado después de se ha seleccionado una sobrecarga. Por lo que el compilador sabe, Tuple.Create<char, int>(char, int) es un candidato válido, y no es ni mejor ni peor que Tuple.Create<char>(char), por lo que el compilador no puede decidir.

+0

Gracias, eso suena muy plausible. ¿Tiene alguna referencia para confirmar esto? – Ani

+0

No es solo plausible, es exacto. Las referencias son difíciles en SO, los votos son anónimos. –

+0

@Hans: Lo siento, no entendí tu comentario. Estaba hablando de documentación, si eso no estaba claro. ¿Cual es tu punto? – Ani

Cuestiones relacionadas