Domanda

Mi chiedo perché il compilatore C # 3.0 non sia in grado di dedurre il tipo di metodo quando viene passato come parametro a una funzione generica quando può implicitamente creare un delegato per lo stesso metodo.

Ecco un esempio:

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
    }   
}

Avrei pensato di poter passare da foo a bar e di fare in modo che il compilatore deducesse il tipo di Action<T> dalla firma della funzione che viene passata, ma non funziona. Tuttavia, posso creare un Action<int> da <=> senza trasmettere, quindi esiste un motivo legittimo per cui il compilatore non può fare la stessa cosa tramite l'inferenza di tipo?

È stato utile?

Soluzione

Forse questo renderà più chiaro:

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 non è un'azione - foo è un gruppo di metodi.

  • Nell'istruzione di assegnazione, il compilatore può dire chiaramente di quale foo stai parlando, poiché il tipo int è specificato.
  • Nell'istruzione barz (foo), il compilatore può dire di quale foo stai parlando, poiché viene specificato il tipo int.
  • Nell'istruzione bar (foo), potrebbe essere qualsiasi pippo con un singolo parametro, quindi il compilatore si arrende.

Modifica: ho aggiunto due (più) modi per aiutare il compilatore a capire il tipo (cioè - come saltare i passaggi di inferenza).

Dalla mia lettura dell'articolo nella risposta di JSkeet, la decisione di non inferire il tipo sembra essere basata su uno scenario di inferenza reciproca, come

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

Poiché il problema generale era irrisolvibile, hanno scelto di lasciare problemi specifici in cui una soluzione esiste come irrisolta.

Come conseguenza di questa decisione, non si aggiungerà un sovraccarico per un metodo e si otterrà molta confusione di tipo da tutti i chiamanti utilizzati in un gruppo di metodi a membro singolo. Immagino sia una buona cosa.

Altri suggerimenti

Il ragionamento è che se il tipo si espande mai non dovrebbe esserci alcuna possibilità di fallimento. vale a dire, se un metodo foo (stringa) viene aggiunto al tipo, non dovrebbe mai importare al codice esistente, purché il contenuto dei metodi esistenti non cambi.

Per tale motivo, anche quando esiste solo un metodo foo, un riferimento a foo (noto come gruppo di metodi) non può essere trasmesso a un delegato non specifico del tipo, come Action<T> ma solo a un tipo- delegato specifico come Action<int>.

Questo è leggermente strano, sì. La specifica C # 3.0 per l'inferenza di tipo è difficile da leggere e contiene errori, ma sembra come dovrebbe funzionare. Nella prima fase (sezione 7.4.2.1) credo che ci sia un errore - non dovrebbe menzionare i gruppi di metodi nel primo punto elenco (poiché non sono coperti dall'inferenza del tipo di parametro esplicito (7.4.2.7) - il che significa che dovrebbe usare inferenza del tipo di output (7.4.2.6). Che sembra come dovrebbe funzionare - ma ovviamente non funziona :(

So che MS sta cercando di migliorare le specifiche per l'inferenza del tipo, quindi potrebbe diventare un po 'più chiaro. So anche che, indipendentemente dalla difficoltà di leggerlo, ci sono restrizioni sui gruppi di metodi e sull'inferenza dei tipi - restrizioni che potrebbero essere espresse in modo speciale quando il gruppo di metodi è effettivamente solo un singolo metodo,

Eric Lippert ha pubblicato un blog su inferenza del tipo restituito non funzionante con i gruppi di metodi che è simile a questo caso - ma qui non siamo interessato al tipo restituito, solo al tipo di parametro. È possibile che altri post nella sua serie di inferenze di tipo può aiutare però.

Tieni presente che l'incarico

Action<int> f = foo;

ha già un sacco di zucchero sintattico. Il compilatore genera effettivamente il codice per questa istruzione:

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

La chiamata del metodo corrispondente viene compilata senza problemi:

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

In seguito, aiuta anche il compilatore a dedurre l'argomento type:

bar<int>(foo);

Quindi si riduce alla domanda, perché lo zucchero nell'istruzione di assegnazione ma non nella chiamata del metodo? Dovrei indovinare che è perché lo zucchero non è ambiguo nell'assegnazione, c'è solo una possibile sostituzione. Ma nel caso delle chiamate di metodo, gli autori del compilatore dovevano già affrontare il problema della risoluzione del sovraccarico. Le cui regole sono abbastanza elaborate. Probabilmente non ci sono riusciti.

Solo per completezza, questo non è specifico per C #: lo stesso codice VB.NET fallisce in modo simile:

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

errore BC32050: non è possibile dedurre il parametro 'T' per 'Barra secondaria pubblica (Of T) (f As System.Action (Of T))'.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top