En primer lugar, permítanme decirles que la respuesta de Jon es correcta. Esta es una de las partes más complicadas de la especificación, tan bueno para Jon por sumergirse en ella de cabeza.
En segundo lugar, déjeme decir que esta línea:
Existe una conversión implícita de un grupo de métodos a un tipo de delegado compatible
(énfasis agregado) es profundamente engañoso y desafortunado. Hablaré con Mads sobre cómo eliminar la palabra "compatible" aquí.
La razón por la que esto es engañoso y desafortunado es porque parece que está llamando a la sección 15.2, "Compatibilidad de delegados". La sección 15.2 describió la relación de compatibilidad entre métodos y tipos de delegados , pero esta es una cuestión de convertibilidad de grupos de métodos y tipos de delegados , que es diferente.
Ahora que lo hemos sacado del camino, podemos revisar la sección 6.6 de la especificación y ver lo que obtenemos.
Para resolver la sobrecarga, primero debemos determinar qué sobrecargas son candidatos aplicables . Un candidato es aplicable si todos los argumentos se pueden convertir implícitamente a los tipos de parámetros formales. Considere esta versión simplificada de su programa:
class Program
{
delegate void D1();
delegate string D2();
static string X() { return null; }
static void Y(D1 d1) {}
static void Y(D2 d2) {}
static void Main()
{
Y(X);
}
}
Así que vamos a repasarlo línea por línea.
Existe una conversión implícita de un grupo de métodos a un tipo de delegado compatible.
Ya he hablado de cómo la palabra "compatible" es desafortunada aquí. Hacia adelante. Nos preguntamos cuando hacemos una resolución de sobrecarga en Y (X), ¿el grupo de métodos X se convierte en D1? ¿Se convierte a D2?
Dado un tipo de delegado D y una expresión E que se clasifica como un grupo de métodos, existe una conversión implícita de E a D si E contiene al menos un método que es aplicable [...] a una lista de argumentos construida mediante el uso del parámetro tipos y modificadores de D, como se describe a continuación.
Hasta aquí todo bien. X puede contener un método que sea aplicable con las listas de argumentos de D1 o D2.
La aplicación en tiempo de compilación de una conversión de un grupo de métodos E a un tipo de delegado D se describe a continuación.
Esta línea realmente no dice nada interesante.
Tenga en cuenta que la existencia de una conversión implícita de E a D no garantiza que la aplicación de la conversión en tiempo de compilación tenga éxito sin errores.
Esta línea es fascinante. ¡Significa que hay conversiones implícitas que existen, pero que están sujetas a convertirse en errores! Esta es una regla extraña de C #. Para divagar un momento, aquí hay un ejemplo:
void Q(Expression<Func<string>> f){}
string M(int x) { ... }
...
int y = 123;
Q(()=>M(y++));
Una operación de incremento es ilegal en un árbol de expresión. Sin embargo, la lambda aún se puede convertir al tipo de árbol de expresión, aunque si alguna vez se usa la conversión, ¡es un error! El principio aquí es que podríamos querer cambiar las reglas de lo que puede ir en un árbol de expresión más adelante; cambiar esas reglas no debería cambiar las reglas del sistema de tipos . Queremos obligarlo a que sus programas sean inequívocos ahora , de modo que cuando cambiemos las reglas de los árboles de expresión en el futuro para mejorarlos, no introduzcamos cambios importantes en la resolución de sobrecarga .
De todos modos, este es otro ejemplo de este tipo de regla extraña. Puede existir una conversión a los efectos de la resolución de sobrecargas, pero su uso real puede ser un error. Aunque, de hecho, esa no es exactamente la situación en la que nos encontramos aquí.
Hacia adelante:
Se selecciona un único método M correspondiente a una invocación de método de la forma E (A) [...] La lista de argumentos A es una lista de expresiones, cada una clasificada como una variable [...] del parámetro correspondiente en el formato formal. -lista-de-parámetros de D.
OKAY. Así que sobrecargamos la resolución en X con respecto a D1. La lista de parámetros formales de D1 está vacía, así que sobrecargamos la resolución en X () y joy, encontramos un método "string X ()" que funciona. De manera similar, la lista de parámetros formales de D2 está vacía. Nuevamente, encontramos que "string X ()" es un método que también funciona aquí.
El principio aquí es que determinar la convertibilidad del grupo de métodos requiere seleccionar un método de un grupo de métodos usando la resolución de sobrecarga , y la resolución de sobrecarga no considera los tipos de retorno .
Si el algoritmo [...] produce un error, se produce un error en tiempo de compilación. De lo contrario, el algoritmo produce un único mejor método M que tiene el mismo número de parámetros que D y se considera que la conversión existe.
Solo hay un método en el grupo de métodos X, por lo que debe ser el mejor. Hemos probado con éxito que existe una conversión de X a D1 y de X a D2.
Ahora bien, ¿esta línea es relevante?
El método seleccionado M debe ser compatible con el tipo de delegado D; de lo contrario, se producirá un error en tiempo de compilación.
De hecho, no, no en este programa. Nunca llegamos a activar esta línea. Porque, recuerde, lo que estamos haciendo aquí es intentar resolver la sobrecarga en Y (X). Tenemos dos candidatos Y (D1) e Y (D2). Ambos son aplicables. ¿Qué es mejor ? En ninguna parte de la especificación describimos la mejora entre estas dos posibles conversiones .
Ahora bien, ciertamente se podría argumentar que una conversión válida es mejor que una que produce un error. Eso estaría diciendo efectivamente, en este caso, que la resolución de sobrecarga SÍ considera los tipos de retorno, que es algo que queremos evitar. La pregunta entonces es qué principio es mejor: (1) mantener el invariante de que la resolución de sobrecarga no considera los tipos de retorno, o (2) intentar elegir una conversión que sabemos que funcionará sobre una que sabemos que no lo hará.
Esta es una llamada de juicio. Con lambdas , que hacemos en cuenta el tipo de retorno en este tipo de conversiones, en el apartado 7.4.3.3:
E es una función anónima, T1 y T2 son tipos delegados o tipos de árboles de expresión con listas de parámetros idénticas, existe un tipo de retorno inferido X para E en el contexto de esa lista de parámetros, y se cumple una de las siguientes:
T1 tiene un tipo de retorno Y1 y T2 tiene un tipo de retorno Y2, y la conversión de X a Y1 es mejor que la conversión de X a Y2
T1 tiene un tipo de retorno Y, y T2 es un retorno nulo
Es lamentable que las conversiones de grupos de métodos y las conversiones lambda sean inconsistentes a este respecto. Sin embargo, puedo vivir con eso.
De todos modos, no tenemos una regla de "mejora" para determinar qué conversión es mejor, X a D1 o X a D2. Por lo tanto, damos un error de ambigüedad en la resolución de Y (X).