Em primeiro lugar, deixe-me apenas dizer que a resposta de Jon está correta. Esta é uma das partes mais cabeludas da especificação, tão boa para Jon mergulhar nela de cabeça.
Em segundo lugar, deixe-me dizer que esta linha:
Existe uma conversão implícita de um grupo de métodos para um tipo de delegado compatível
(ênfase adicionada) é profundamente enganosa e infeliz. Vou ter uma conversa com Mads sobre como remover a palavra "compatível" aqui.
A razão pela qual isso é enganoso e lamentável é porque parece que isso está chamando a atenção para a seção 15.2, "Compatibilidade de delegados". A Seção 15.2 descreveu a relação de compatibilidade entre métodos e tipos de delegado , mas esta é uma questão de conversibilidade de grupos de métodos e tipos de delegado , que é diferente.
Agora que já resolvemos isso, podemos percorrer a seção 6.6 da especificação e ver o que temos.
Para fazer a resolução de sobrecarga, precisamos primeiro determinar quais sobrecargas são candidatas aplicáveis . Um candidato é aplicável se todos os argumentos forem implicitamente conversíveis para os tipos de parâmetros formais. Considere esta versão simplificada do seu 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);
}
}
Então, vamos analisar linha por linha.
Existe uma conversão implícita de um grupo de métodos para um tipo de delegado compatível.
Já discuti como a palavra "compatível" é lamentável aqui. Se movendo. Estamos nos perguntando, ao fazer a resolução de sobrecarga em Y (X), o grupo de métodos X é convertido para D1? Ele converte para D2?
Dado um delegado tipo D e uma expressão E que é classificada como um grupo de métodos, existe uma conversão implícita de E para D se E contiver pelo menos um método que seja aplicável [...] a uma lista de argumentos construída pelo uso do parâmetro tipos e modificadores de D, conforme descrito a seguir.
Por enquanto, tudo bem. X pode conter um método aplicável às listas de argumentos de D1 ou D2.
A aplicação de tempo de compilação de uma conversão de um grupo de métodos E para um tipo delegado D é descrita a seguir.
Esta linha realmente não diz nada de interessante.
Observe que a existência de uma conversão implícita de E para D não garante que a aplicação de tempo de compilação da conversão será bem-sucedida sem erros.
Essa linha é fascinante. Isso significa que existem conversões implícitas que existem, mas que estão sujeitas a serem transformadas em erros! Esta é uma regra bizarra do C #. Para divagar um momento, aqui está um exemplo:
void Q(Expression<Func<string>> f){}
string M(int x) { ... }
...
int y = 123;
Q(()=>M(y++));
Uma operação de incremento é ilegal em uma árvore de expressão. No entanto, o lambda ainda é conversível para o tipo de árvore de expressão, mesmo que se a conversão for usada, é um erro! O princípio aqui é que podemos querer mudar as regras do que pode ir em uma árvore de expressão posteriormente; alterar essas regras não deve alterar as regras do sistema de tipo . Queremos forçá-lo a tornar seus programas inequívocos agora , de modo que, quando alterarmos as regras para árvores de expressão no futuro para torná-las melhores, não introduzamos alterações significativas na resolução de sobrecarga .
De qualquer forma, este é outro exemplo desse tipo de regra bizarra. Uma conversão pode existir para fins de resolução de sobrecarga, mas pode ser um erro para realmente usar. Embora, na verdade, essa não seja exatamente a situação em que estamos aqui.
Se movendo:
Um único método M é selecionado correspondendo a uma invocação de método da forma E (A) [...] A lista de argumentos A é uma lista de expressões, cada uma classificada como uma [...] variável do parâmetro correspondente no formal -lista de parâmetros de D.
ESTÁ BEM. Portanto, sobrecarregamos a resolução em X em relação a D1. A lista de parâmetros formal de D1 está vazia, então fazemos resolução de sobrecarga em X () e joy, encontramos um método "string X ()" que funciona. Da mesma forma, a lista formal de parâmetros de D2 está vazia. Novamente, descobrimos que "string X ()" é um método que funciona aqui também.
O princípio aqui é que determinar a conversibilidade do grupo de métodos requer a seleção de um método de um grupo de métodos usando a resolução de sobrecarga , e a resolução de sobrecarga não considera os tipos de retorno .
Se o algoritmo [...] produzir um erro, ocorrerá um erro em tempo de compilação. Caso contrário, o algoritmo produz um único melhor método M com o mesmo número de parâmetros que D e a conversão é considerada existente.
Existe apenas um método no grupo de métodos X, portanto, deve ser o melhor. Provamos com sucesso que existe uma conversão de X para D1 e de X para D2.
Agora, esta linha é relevante?
O método selecionado M deve ser compatível com o delegado tipo D, caso contrário, ocorrerá um erro de tempo de compilação.
Na verdade, não, não neste programa. Nunca chegamos ao ponto de ativar essa linha. Porque, lembre-se, o que estamos fazendo aqui é tentar resolver a sobrecarga em Y (X). Temos dois candidatos Y (D1) e Y (D2). Ambos são aplicáveis. Qual é melhor ? Em nenhum lugar da especificação descrevemos a melhor relação entre essas duas conversões possíveis .
Agora, alguém certamente poderia argumentar que uma conversão válida é melhor do que aquela que produz um erro. Isso significaria efetivamente, neste caso, que a resolução de sobrecarga considera os tipos de retorno, que é algo que queremos evitar. A questão então é qual princípio é melhor: (1) manter o invariante de que a resolução de sobrecarga não considera os tipos de retorno, ou (2) tentar escolher uma conversão que sabemos que funcionará sobre outra que sabemos que não funcionará?
Este é um julgamento. Com lambdas , nós fazer considerar o tipo de retorno nesses tipos de conversões, na seção 7.4.3.3:
E é uma função anônima, T1 e T2 são tipos de delegado ou tipos de árvore de expressão com listas de parâmetros idênticas, um tipo de retorno inferido X existe para E no contexto dessa lista de parâmetros e um dos seguintes é válido:
T1 tem um tipo de retorno Y1 e T2 tem um tipo de retorno Y2, e a conversão de X para Y1 é melhor do que a conversão de X para Y2
T1 tem um tipo de retorno Y e T2 não tem retorno
É lamentável que as conversões de grupo de métodos e conversões lambda sejam inconsistentes a esse respeito. No entanto, posso viver com isso.
De qualquer forma, não temos uma regra de "melhoras" para determinar qual conversão é melhor, X para D1 ou X para D2. Portanto, fornecemos um erro de ambigüidade na resolução de Y (X).