C # 3.0 genérico inferência de tipos - passando um delegado como um parâmetro de função
-
03-07-2019 - |
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?
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