Pergunta

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

Aqui está um exemplo:

class Test
{
    static void foo(int x) { }
    static void bar<T>(Action<T> f) { }

    static void test()
    {
        Action<int> 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<T> da assinatura da função que está sendo passado, mas isso não funciona. No entanto posso criar uma Action<int> de foo sem lançar assim há uma razão legítima que o compilador não poderia também fazer a mesma coisa através do tipo de inferência?

Foi útil?

Solução

Talvez isto irá tornar mais claro:

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

foo não é uma ação -. Foo é um método de grupo

  • No comando de atribuição, o compilador pode dizer claramente que foo você está falando, já que o tipo int é especificado.
  • Na declaração Barz (foo), o compilador pode dizer que foo você está falando, já que o tipo int é especificado.
  • Na declaração bar (foo), poderia ser qualquer foo com um único parâmetro -. Para que o compilador desiste

Edit: Eu adicionei duas (mais) formas de ajudar a figura compilador o tipo (ou seja - como ignorar as etapas de inferência)

.

De minha leitura do artigo em resposta de JSkeet, a decisão de não inferir o tipo parece estar baseada em um cenário inferir mútuo, como

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

Uma vez que o problema geral foi unsolve-capazes, eles escolhem para problemas específicos esquerda, onde existe uma solução como sem solução.

Como consequência desta decisão, você não vai ser adicionando uma sobrecarga de um método e recebendo um monte de tipo de confusão de todas as chamadas que são usados ??para um grupo de método único membro. Eu acho que é uma coisa boa.

Outras dicas

O raciocínio é que, se o tipo que nunca se expande não deve haver nenhuma possibilidade de falha. ou seja, se um foo método (string) é adicionado ao tipo, ele nunca deve assunto ao código existente -. contanto que o conteúdo de métodos existentes não mudam

Por essa razão, mesmo quando há apenas um foo método, uma referência a foo (conhecida como um método de grupo) não pode ser convertido para um delegado não-específicos do tipo, tais como Action<T> mas apenas a um delegado de tipo específico tais como Action<int>.

Isso é um pouco estranho, sim. O C # 3.0 especificação para o tipo de inferência é difícil de ler e tem erros nele, mas aparência como ele deve funcionar. Na primeira fase (seção 7.4.2.1) Eu acredito que há um erro - ele não deve mencionar grupos de métodos no primeiro marcador (como eles não estão cobertos pela explícita tipo de parâmetro inferência (7.4.2.7) - o que significa que deve usar tipo de saída inferência (7.4.2.6) que looks como ele deve funcionar - mas obviamente não faz:. (

Eu sei que MS está olhando para melhorar a especificação para o tipo de inferência, por isso pode se tornar um pouco mais claro. Também sei que, independentemente da dificuldade de lê-lo, há restrições sobre grupos de métodos e inferência de tipos -. Restrições que poderiam ser especial-encaixotado quando o grupo método só é realmente um método único, reconhecidamente

Eric Lippert tem uma entrada de blog em retorno inferência de tipos não trabalhar com grupos de métodos que é semelhante a este caso - mas aqui não estamos interessado no tipo de retorno, apenas no tipo de parâmetro. É possível que outras mensagens em sua série inferência tipo pode ajudar embora.

Tenha em mente que a atribuição

Action<int> f = foo;

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

Action<int> f = new Action<int>(foo);

Os correspondentes compila chamada de método sem problema:

bar(new Action<int>(foo));

Fwiw, por isso não ajudar o compilador para deduzir o argumento do tipo:

bar<int>(foo);

Então, tudo se resume à pergunta: por que o açúcar na instrução de atribuição, mas não na chamada de método? Eu teria que acho que é porque o açúcar é inequívoca na atribuição, há apenas uma possível substituição. Mas no caso de chamadas de método, os escritores compilador já teve de lidar com o problema de resolução de sobrecarga. As regras de que são bastante elaborado. Eles provavelmente só não dar a volta a isso.

Apenas para ser completo, isto não é específico para C #: O mesmo código VB.NET falha semelhante:

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:. Tipo de parâmetro 'T' para 'Public Sub bar (Of T) (f Como System.Action (Of T))' não pode ser inferida

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top