Inferência de tipos genéricos do C # 3.0 – passando um delegado como um parâmetro de function

Eu estou querendo saber por que o compilador C # 3.0 não consegue inferir o tipo de um método quando ele é passado como um parâmetro para uma function genérica quando ele pode implicitamente criar um delegado para o mesmo método.

Aqui está um exemplo:

class Test { static void foo(int x) { } static void bar(Action f) { } static void test() { Action f = foo; // I can do this bar(f); // and then do this bar(foo); // but this does not work } } 

Eu teria pensado que eu seria capaz de passar foo para bar e ter o compilador inferir o tipo de Action da assinatura da function que está sendo passada, mas isso não funciona. No entanto, eu posso criar uma Action de foo sem conversão, então existe uma razão legítima que o compilador não poderia fazer a mesma coisa por meio de inferência de tipos?

    Talvez isso torne isso mais claro:

     public class SomeClass { static void foo(int x) { } static void foo(string s) { } static void bar(Action f){} static void barz(Action f) { } static void test() { Action f = foo; bar(f); barz(foo); bar(foo); //these help the compiler to know which types to use bar(foo); bar( (int i) => foo(i)); } } 

    foo não é uma ação – foo é um grupo de methods.

    • Na instrução de atribuição, o compilador pode dizer claramente de que foo você está falando, já que o tipo int é especificado.
    • Na instrução barz (foo), o compilador pode dizer de que foo você está falando, já que o tipo int é especificado.
    • Na instrução bar (foo), pode ser qualquer foo com um único parâmetro – então o compilador desiste.

    Edit: Eu adicionei duas (mais) maneiras de ajudar o compilador descobrir o tipo (ou seja, como pular as etapas de inferência).

    Da minha leitura do artigo na resposta do JSkeet, a decisão de não inferir o tipo parece ser baseada em um cenário de inferência mútua, como

      static void foo(T x) { } static void bar(Action f) { } static void test() { bar(foo); //wut's T? } 

    Como o problema geral era insolúvel, eles escolheram deixar problemas específicos nos quais uma solução existe como não resolvida.

    Como consequência dessa decisão, você não estará adicionando uma sobrecarga para um método e obtendo um monte de confusão de tipos de todos os chamadores que são usados ​​para um grupo de methods de único membro. Eu acho que é uma coisa boa.

    O raciocínio é que, se o tipo se expandir, não haverá possibilidade de falha. isto é, se um método foo (string) é adicionado ao tipo, ele nunca deve importar para o código existente – contanto que o conteúdo dos methods existentes não mude.

    Por esse motivo, mesmo quando há apenas um método foo, uma referência a foo (conhecido como um grupo de methods) não pode ser convertida em um delegado não específico do tipo, como Action mas apenas para um delegado específico do tipo como Action .

    Isso é um pouco estranho, sim. A especificação do C # 3.0 para inferência de tipos é difícil de ler e tem erros, mas parece que deve funcionar. Na primeira fase (seção 7.4.2.1) eu acredito que há um erro – ele não deve mencionar grupos de methods no primeiro marcador (como eles não são cobertos por inferência explícita de tipo de parâmetro (7.4.2.7) – o que significa que ele deve usar inferência tipo de saída (7.4.2.6) .Isso parece que deve funcionar – mas, obviamente, não faz 🙁

    Eu sei que a MS está procurando melhorar a especificação para inferência de tipos, então pode se tornar um pouco mais clara. Eu também sei que, independentemente da dificuldade de lê-lo, há restrições sobre grupos de methods e inferência de tipos – restrições que podem ser especiais quando o grupo de methods é apenas um método, reconhecidamente.

    Eric Lippert tem uma input de blog sobre inferência de tipo de retorno não funcionando com grupos de methods que é semelhante a este caso – mas aqui não estamos interessados ​​no tipo de retorno, apenas no tipo de parâmetro. É possível que outros posts em sua série de inferência de tipos possam ajudar.

    Tenha em mente que a tarefa

     Action f = foo; 

    já tem muito açúcar sintático. O compilador realmente gera código para esta declaração:

     Action f = new Action(foo); 

    A chamada de método correspondente compila sem problema:

     bar(new Action(foo)); 

    Fwiw, ajuda o compilador a deduzir o argumento type:

     bar(foo); 

    Então, tudo se resume à pergunta: por que o açúcar na declaração de atribuição, mas não na chamada do método? Eu tenho que adivinhar que é porque o açúcar é inequívoco na atribuição, há apenas uma substituição possível. Mas no caso de chamadas de método, os escritores do compilador já tinham que lidar com o problema de resolução de sobrecarga. As regras são bastante elaboradas. Eles provavelmente não conseguiram chegar lá.

    Apenas para completar, isso não é específico para C #: O mesmo código VB.NET falha da mesma forma:

     Imports System Module Test Sub foo(ByVal x As integer) End Sub Sub bar(Of T)(ByVal f As Action(Of T)) End Sub Sub Main() Dim f As Action(Of integer) = AddressOf foo ' I can do this bar(f) ' and then do this bar(AddressOf foo) ' but this does not work End Sub End Module 

    Erro BC32050: O parâmetro de tipo ‘T’ para ‘Public Sub bar (Of T) (f Como System.Action (Of T))’ não pode ser inferido.