Por que a compilação está correta quando uso o método Invoke e não está correta quando eu retorno Func <int, int> diretamente?

28

Eu não entendo este caso:

public delegate int test(int i);

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
}

public test Fail()
{
    Func<int, int> f = x => x;
    return f; // <- code doesn't compile
}

Por que a compilação está correta quando uso o Invokemétodo e não correta quando retorno csharp Func<int,int>diretamente?

Evgeniy Terekhin
fonte
Você tem um delegado, o que significa que você está recebendo algum tipo de evento. A chamada evita a exceção de thread cruzado e permite que vários processos acessem o objeto.
jdweng 9/03
Observe que você verá esse problema mesmo se usar dois delegados com aparência idêntica, como delegate void test1(int i);edelegate void test2(int i);
Matthew Watson

Respostas:

27

Há duas coisas que você precisa saber para entender esse comportamento.

  1. Todos os delegados derivam de System.Delegate, mas delegados diferentes têm tipos diferentes e, portanto, não podem ser atribuídos um ao outro.
  2. A linguagem C # fornece tratamento especial para atribuir um método ou lambda a um delegado .

Como delegados diferentes têm tipos diferentes, isso significa que você não pode atribuir um delegado de um tipo para outro.

Por exemplo, dado:

delegate void test1(int i);
delegate void test2(int i);

Então:

test1 a = Console.WriteLine; // Using special delegate initialisation handling.
test2 b = a;                 // Using normal assignment, therefore does not compile.

A primeira linha acima compila OK porque está usando o tratamento especial para atribuir um lambda ou um método a um delegado.

De fato, essa linha é efetivamente reescrita assim pelo compilador:

test1 a = new test1(Console.WriteLine);

A segunda linha acima não é compilada porque está tentando atribuir uma instância de um tipo a outro tipo incompatível.

No que diz respeito aos tipos, não há atribuição compatível entre test1e test2porque são tipos diferentes.

Se ajudar a pensar sobre isso, considere esta hierarquia de classes:

class Base
{
}

class Test1 : Base
{
}

class Test2 : Base
{
}

O código a seguir não irá compilar, embora Test1e Test2derivam da mesma classe base:

Test1 test1 = new Test1();
Test2 test2 = test1; // Compile error.

Isso explica por que você não pode atribuir um tipo de delegado a outro. Essa é apenas a linguagem C # normal.

No entanto, o importante é entender por que você pode atribuir um método ou lambda a um representante compatível. Como observado acima, isso faz parte do suporte ao idioma C # para delegados.

Então, finalmente, para responder à sua pergunta:

Quando você usa, Invoke()está atribuindo uma chamada de MÉTODO ao delegado, usando a manipulação de linguagem C # especial para atribuir métodos ou lambdas a um delegado, em vez de tentar atribuir um tipo incompatível - portanto, ele compila OK.

Para ser completamente claro, o código que é compilado no seu OP:

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
}

Na verdade, é convertido conceitualmente para algo como:

public test Success()
{
    Func<int, int> f = x => x;
    return new test(f.Invoke);
}

Enquanto o código com falha está tentando atribuir entre dois tipos incompatíveis:

public test Fail()
{
    Func<int, int> f = x => x;
    return f; // Attempting to assign one delegate type to another: Fails
}
Matthew Watson
fonte
6

No segundo caso, fé do tipo Func<int, int>, mas diz-se que o método retorna a test. Como são tipos não relacionados (delegados), que não são conversíveis entre si, ocorre um erro do compilador. Você pode ir para esta seção das especificações de idioma e procurar "delegar". Você não encontrará menção de conversões entre delegados com as mesmas assinaturas.

No primeiro caso, no entanto, f.Invokeé uma expressão de grupo de métodos , que na verdade não possui um tipo. O compilador C # converterá expressões de grupo de métodos em tipos de delegados específicos de acordo com o contexto, por meio de uma conversão de grupo de métodos .

(Citando a 5a bala aqui , ênfase minha)

Uma expressão é classificada como uma das seguintes opções:

  • ...

  • Um grupo de métodos, que é um conjunto de métodos sobrecarregados resultantes de uma consulta de membro. [...] Um grupo de métodos é permitido em uma expressão de invocação, em uma expressão de delegação de criação e no lado esquerdo de um isoperador e pode ser implicitamente convertido em um tipo de delegado compatível.

Nesse caso, é convertido para o testtipo de delegado.

Em outras palavras, return fnão funciona porque fjá possui um tipo, mas f.Invokeainda não possui um tipo.

Vassoura
fonte
2

O problema aqui é a compatibilidade de tipos:

A seguir está a definição de delegado do Func de fontes do MSDN:

public delegate TResult Func<in T, out TResult>(T arg);

Se você vir uma relação direta entre o Func mencionado acima e o seu Delegado definido:

public delegate int test(int i);

Por que o primeiro snippet é compilado:

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
 }

Os delegados são comparados usando a assinatura, que é os parâmetros de entrada e o resultado da saída; em última análise, um delegado é um ponteiro de função e duas funções podem ser comparadas apenas via assinatura. No tempo de execução, o método chamado via Func é atribuído ao Testdelegado, uma vez que Signature é o mesmo, ele funciona perfeitamente. É uma atribuição de ponteiro de função, na qual Testdelegado agora chamará o método apontado pelo delegado Func

Por que o segundo snippet falha ao compilar

Entre o Func e o delegado de teste, não há compatibilidade de tipo / atribuição, o Func não pode preencher como parte das regras do sistema Tipo. Mesmo quando seu resultado pode ser atribuído e preenchido test delegatecomo foi feito no primeiro caso.

Mrinal Kamboj
fonte