Perché la compilazione è OK quando utilizzo il metodo Invoke e non OK quando restituisco Func <int, int> direttamente?


28

Non capisco questo 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
}

Perché la compilazione è OK quando utilizzo il Invokemetodo e non OK quando ritorno csharp Func<int,int>direttamente?


Hai un delegato, il che significa che stai ricevendo una sorta di evento. Invoke impedisce l'eccezione cross-thread e consente a più processi di accedere all'oggetto.
jdweng

Nota che vedrai questo problema anche se usi due delegati identici come delegate void test1(int i);edelegate void test2(int i);
Matthew Watson,

Risposte:


27

Ci sono due cose che devi sapere per capire questo comportamento.

  1. Tutti i delegati derivano System.Delegate, ma delegati diversi hanno tipi diversi e pertanto non possono essere assegnati l'uno all'altro.
  2. Il linguaggio C # fornisce una gestione speciale per l'assegnazione di un metodo o lambda a un delegato .

Poiché delegati diversi hanno tipi diversi, ciò significa che non è possibile assegnare un delegato di un tipo a un altro.

Ad esempio, dato:

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

Poi:

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

La prima riga sopra viene compilata OK perché utilizza la gestione speciale per assegnare un lambda o un metodo a un delegato.

In effetti, questa riga viene effettivamente riscritta in questo modo dal compilatore:

test1 a = new test1(Console.WriteLine);

La seconda riga sopra non viene compilata perché sta tentando di assegnare un'istanza di un tipo a un altro tipo incompatibile.

Per quanto riguarda i tipi, non esiste un'assegnazione compatibile tra test1e test2perché sono tipi diversi.

Se aiuta a pensarci, considera questa gerarchia di classi:

class Base
{
}

class Test1 : Base
{
}

class Test2 : Base
{
}

Il seguente codice NON verrà compilato, anche se Test1e Test2deriverà dalla stessa classe di base:

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

Questo spiega perché non è possibile assegnare un tipo delegato a un altro. Questo è solo il normale linguaggio C #.

Tuttavia, la cosa cruciale è capire perché ti è permesso assegnare un metodo o lambda a un delegato compatibile. Come notato sopra, questo fa parte del supporto del linguaggio C # per i delegati.

Quindi finalmente per rispondere alla tua domanda:

Quando si utilizza Invoke()si assegna una chiamata METHOD al delegato utilizzando la speciale gestione del linguaggio C # per assegnare metodi o lambdas a un delegato anziché tentare di assegnare un tipo incompatibile, quindi viene compilato OK.

Per essere completamente chiari, il codice che compila nel tuo OP:

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

In realtà viene convertito concettualmente in qualcosa del tipo:

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

Considerando che il codice in errore sta tentando di assegnare tra due tipi incompatibili:

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

6

Nel secondo caso, fè di tipo Func<int, int>, ma si dice che il metodo restituisca a test. Si tratta di tipi non correlati (delegati), non convertibili l'uno con l'altro, quindi si verifica un errore del compilatore. Puoi andare in questa sezione delle specifiche della lingua e cercare "delegato". Non troverai alcuna menzione delle conversioni tra delegati che hanno le stesse firme.

Nel primo caso, tuttavia, f.Invokeè un'espressione del gruppo di metodi , che in realtà non ha un tipo. Il compilatore C # convertirà le espressioni del gruppo di metodi in tipi delegati specifici in base al contesto, attraverso una conversione del gruppo di metodi .

(Citando il quinto proiettile qui , enfatizza il mio)

Un'espressione è classificata come una delle seguenti:

  • ...

  • Un gruppo di metodi, che è un insieme di metodi sovraccarichi risultanti dalla ricerca di un membro. [...] Un gruppo di metodi è consentito in invocation_expression, delegate_creation_expression e come lato sinistro di un isoperatore e può essere implicitamente convertito in un tipo delegato compatibile.

In questo caso, viene convertito nel testtipo delegato.

In altre parole, return fnon funziona perché fha già un tipo, ma f.Invokenon ha ancora un tipo.


2

Il problema qui è Compatibilità di tipo:

Di seguito è la definizione del delegato di Func dalle fonti MSDN:

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

Se vedi che non esiste alcuna relazione diretta tra il Func menzionato sopra e il tuo delegato definito:

public delegate int test(int i);

Perché viene compilato il primo frammento:

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

I delegati vengono confrontati utilizzando la firma, ovvero i parametri di input e il risultato di output, in definitiva un delegato è un puntatore a funzione e due funzioni possono essere confrontate solo tramite firma. In fase di esecuzione il metodo invocato tramite Func viene assegnato al Testdelegato, poiché Signature è lo stesso e funziona senza problemi. È un'assegnazione del puntatore a funzione, in cui il Testdelegato ora invocherà il metodo indicato dal delegato Func

Perché la compilazione del 2o frammento non riesce

Tra Func e il delegato del test, non esiste compatibilità di tipo / assegnazione, Func non può compilare come parte delle regole di sistema Tipo. Anche quando il suo risultato può essere assegnato e compilato test delegatecome fatto nel primo caso.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.