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:
T1
è int
.
Fun2
assegnato a Func<int, T2>
per alcuni T2
.
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.
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. –
Ma c'è un Fun2 in primo luogo, quindi non "stringe" veramente nulla. –
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. –