2015-09-17 9 views
17

Si consideri il seguente esempio:"a due livelli" inferenza metodo di argomento generico con delega

class Test 
{ 
    public void Fun<T>(Func<T, T> f) 
    { 
    } 

    public string Fun2(string test) 
    { 
     return ""; 
    } 

    public Test() 
    { 
     Fun<string>(Fun2); 
    } 
} 

Questo compila bene.

Mi chiedo perché non riesco a rimuovere l'argomento generico <string>? Ottengo un errore che non può essere dedotto dall'utilizzo.

Capisco che tale deduzione potrebbe essere difficile per il compilatore, ma tuttavia sembra possibile.

Vorrei una spiegazione di questo comportamento.

Modifica rispondendo risposta di Jon Hanna:

Allora perché questo funziona?

class Test 
{ 
    public void Fun<T1, T2>(T1 a, Func<T1, T2> f) 
    { 
    } 

    public string Fun2(int test) 
    { 
     return test.ToString(); 
    } 

    public Test() 
    { 
     Fun(0, Fun2); 
    } 
} 

Qui mi legano solo un parametro con T1 a, ma sembra T2 ad essere altrettanto difficile.

+0

Con la tua modifica, hai ora fornito il compilatore tramite la chiamata 'Fun (0, Fun2); Il compilatore ora sa che ha bisogno di un metodo, dal gruppo di metodi 'Fun2', che ha un tipo' T1', in questo caso 'int'. Questo riduce ad un metodo e quindi può dedurre quale usare. –

+0

Ma c'è un Fun2 in primo luogo, quindi non "stringe" veramente nulla. –

+0

Il compilatore C# si rifiuta di trattare un gruppo di metodi contenente un solo metodo come delegato. È necessario fornire informazioni per selezionare un metodo dal gruppo affinché si preoccupi di farlo. Sarebbe bello se guardasse il gruppo, capì che c'era un solo metodo e lo selezionò, ma purtroppo non è così che funziona. –

risposta

13

Non si può inferire il tipo, perché il tipo non è definito qui.

Fun2 non è un Func<string, string>, è comunque qualcosa che può essere assegnato a Func<string, string>.

quindi se si utilizza:

public Test() 
{ 
    Func<string, string> del = Fun2; 
    Fun(del); 
} 

Oppure:

public Test() 
{ 
    Fun((Func<string, string>)Fun2); 
} 

Poi si sta creando in modo esplicito un Func<string, string> da Fun2, e generico tipo di inferenza funziona di conseguenza.

Al contrario, quando si esegue:

public Test() 
{ 
    Fun<string>(Fun2); 
} 

Poi l'insieme dei sovraccarichi Fun<string> contiene solo uno che accetta un Func<string, string> e il compilatore può dedurre che si desidera utilizzare Fun2 in quanto tale.

Ma si sta chiedendo di dedurre sia il tipo generico basato sul tipo dell'argomento, sia il tipo dell'argomento basato sul tipo generico. Questa è una domanda più grande di entrambi i tipi di inferenza che può fare.

(Vale la pena se si considera che in .NET 1.0 non solo erano i delegati non generico-così si sarebbe dovuto definire delgate string MyDelegate(string test) -ma è stato anche necessario per creare l'oggetto con un costruttore Fun(new MyDelegate(Fun2)). La sintassi è cambiato a fare uso dei delegati più semplice in molti modi, ma l'uso implicito di Fun2 come Func<string, string> è ancora una costruzione di un oggetto delegato dietro le quinte).

Allora perché funziona?

class Test 
{ 
    public void Fun<T1, T2>(T1 a, Func<T1, T2> f) 
    { 
    } 

    public string Fun2(int test) 
    { 
     return test.ToString(); 
    } 

    public Test() 
    { 
     Fun(0, Fun2); 
    } 
} 

Perché allora può dedurre, in ordine:

  1. T1 è int.
  2. Fun2 assegnato a Func<int, T2> per alcuni T2.
  3. Fun2 può essere assegnato a un Func<int, T2> se T2 è string. Pertanto T2 è una stringa.

In particolare, il tipo di ritorno di Func può essere dedotto da una funzione una volta ottenuti i tipi di argomento. Questo è altrettanto valido (e vale lo sforzo da parte del compilatore) perché è importante in Select di Linq. Ciò fa emergere un caso correlato, il fatto che con il numero x.Select(i => i.ToString()) non abbiamo abbastanza informazioni per sapere a cosa serve la lambda. Una volta che sappiamo se lo x è IEnumerable<T> o IQueryable<T> sappiamo che abbiamo Func<T, ?> o Expression<Func<T, ?>> e il resto può essere dedotto da lì.

Vale anche la pena notare che dedurre il tipo di reso non è soggetto ad un'ambiguità che deduce gli altri tipi. Considera se abbiamo avuto sia il tuo Fun2 (quello che prende uno string e quello che prende uno int) nella stessa classe. Questo è un sovraccarico C# valido, ma deduce il tipo di Func<T, string> che è possibile eseguire il cast di Fun2 impossibile; entrambi sono validi.

Tuttavia, mentre .NET consente l'overloading sul tipo restituito, C# non lo fa. Quindi nessun programma C# valido può essere ambiguo sul tipo di ritorno di uno Func<T, TResult> creato da un metodo (o lambda) una volta determinato il tipo di T. Quella relativa facilità, unita alla grande utilità, lo rende qualcosa che il compilatore può benissimo dedurre per noi.

+0

ovviamente la mia modifica è stata ispirata da "Seleziona".Intendi dire che è stato più semplice implementare l'inferenza del tipo di ritorno in questo caso? Per evitare problemi con sovraccarichi? // Modifica, questo è quello che hai scritto, abbiamo avuto una gara ;-) Mi piacerebbe vedere che in ECMA, avrebbe dovuto scavare. –

+0

Appena aggiunto un po 'al fatto che è davvero più semplice, perché in tutti i casi è possibile, mentre in alcuni casi il tipo di parametro inferito è ambiguo. (E peggio ancora, se hanno aggiunto il supporto per i casi non ambigui, è un codice che può essere rotto da aggiunte ragionevoli che lo rendono di nuovo ambiguo). –

+0

perché non lo inferisce? –

3

Si sta cercando il compilatore di dedurre string da Fun2, che è troppo chiedere per il compilatore C#. Questo perché vede Fun2 come un gruppo di metodi, non un delegato, quando viene fatto riferimento in Test.

Se si modifica il codice di passare nel parametro necessaria per Fun2 e invocare da Fun, quindi la necessità va via, come adesso avete un parametro string, permettendo il tipo da dedurre:

class Test 
{ 
    public void Fun<T>(Func<T, T> f, T x) 
    { 
     f(x); 
    } 

    public string Fun2(string test) 
    { 
     return test; 
    } 

    public Test() 
    { 
     Fun(Fun2, ""); 
    } 
} 

Per rispondere alla versione modificata della domanda, fornendo il tipo T1 al quale è stato fornito il compilatore - tramite la chiamata Fun(0, Fun2); - informazioni aggiuntive. Ora sa che ha bisogno di un metodo, dal gruppo di metodi Fun2, che ha un parametro T1, in questo caso int. Questo riduce ad un metodo e quindi può dedurre quale usare.