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.
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
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
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