2010-01-21 21 views
12

Supponiamo che io sonoNET: i tipi generici dedotto sui metodi statici

public static List<T2> Map<T,T2>(List<T> inputs, Func<T, T2> f) 
{ 
    return inputs.ConvertAll((x) => f(x)); 
} 

private int Square(int x) { return x*x; } 

public void Run() 
{ 
    var inputs = new List<Int32>(new int[]{2,4,8,16,32,64,128,256,512,1024,2048}); 

    // this does not compile 
    var outputs = Map(inputs, Square); 

    // this is fine 
    var outputs2 = Map<Int32,Int32>(inputs, Square); 

    // this is also fine (thanks, Jason) 
    var outputs2 = Map<Int32,Int32>(inputs, (x)=>x*x); 

    // also fine 
    var outputs2 = Map(inputs, (x)=>x*x); 
} 

Perché non compilare?

EDIT: L'errore è:

errore CS0411: Gli argomenti di tipo per il metodo 'Namespace.Map < T, T2 > (System.Collections.Generic.List <T>, System.Func < T, T2 >) "non può essere dedotto dall'utilizzo. Prova a specificare esplicitamente gli argomenti del tipo.

Perché devo specificare il tipo di funzione Map()? Non può dedurre questo dal passato Func<T>? (Nel mio caso, Piazza)


Se la soluzione lo stesso che per
C# 3.0 generic type inference - passing a delegate as a function parameter?

+0

Interessante. Ti dispiacerebbe postare l'errore del compilatore? –

+0

Qual è il risultato se si modifica la var nella prima riga di Run() su List ? –

+0

@tehMick: il risultato sarà lo stesso. Il tipo è dedotto. –

risposta

8

Dal tuo messaggio di errore:

Il tipo di argomenti per il metodo '[...].Map<T,T2>(System.Collections.Generic.List<T>, System.Func<T,T2>)' non possono essere dedotti dal loro utilizzo. Prova a specificare esplicitamente gli argomenti del tipo.

Si noti che il messaggio di errore indica che non è possibile capire gli argomenti di tipo. Cioè, ha problemi a risolvere uno dei parametri tipo T o T2. Questo a causa di §25.6.4 (Inference of type argument) della specifica. Questa è la parte della specifica che tratta di inferire parametri di tipo generico.

Nulla viene dedotta dalla argomento (ma inferenza riesce) se una qualsiasi delle seguenti condizioni:

[...]

L'argomento è un gruppo metodo.

Pertanto, il compilatore non è in grado di utilizzare il tipo delegato di Square per dedurre il tipo di T2. Si noti che se si modifica la dichiarazione di

public static List<T> Map<T>(List<T> inputs, Func<T, T> f) { 
     return inputs.ConvertAll((x) => f(x)); 
} 

poi

var outputs = Map(inputs, Square); 

è legale. In questo caso, è già stato deciso che è dal fatto che inputs è un List<int>.

Ora, la domanda più profonda è perché la suddetta specifica? Cioè, perché i gruppi di metodi non hanno un ruolo nella risoluzione dei parametri di tipo? Penso che sia a causa di casi come questo:

class Program { 
    public static T M<T>(Func<T, T> f) { 
     return default(T); 
    } 

    public static int F(int i) { 
     return i; 
    } 

    public static float F(float f) { 
     return f; 
    } 

    static void Main(string[] args) { 
     M(F); // which F am I? 
    } 
} 
+0

Solo per convalida multipla, IanG da questo forum MSDN C# sembra dire la stessa cosa e va in un po 'di il Why the spec dice così: http://social.msdn.microsoft.com/Forums/en/csharplanguage/thread/a4847737-4a6b-4fcd-89f2-1b213aaf8422 L'ho trovato anche un po 'illuminante. –

+0

@jdk: Hmm ... vedi la mia modifica. – jason

+0

'Mappa (Elenco ingressi, Func f)'? Non molto di una funzione mappa se input e output sono dello stesso tipo, eh? ;) – Juliet

2

La deduzione non riesce a dedurre il tipo del delegato, non è la lista:

// this is also fine 
var outputs3 = Map(inputs, new Func<int, int>(Square)); 

// more calls that compile correctly 
var outputs4 = Map(inputs, x => Square(x)); 

var outputs5 = Map(inputs, x => x * x); 

Func<int, int> t = Square; 
var outputs6 = Map(inputs, t); 

Non so il motivo per cui, anche se - forse non c'è proprio nessun typecast implicita dalla firma del Square-Func<Int32, Int32>? Sembra strano che Func<int, int> t = Square; sia valido, ma il compilatore non può fare il salto da solo ... Bug, forse?

+0

Leggere il messaggio di errore. Gli argomenti del tipo per il metodo ''[...]. Mappa (System.Collections.Generic.List , System.Func )''. Non riesce a capire quali siano i parametri di tipo generico. Può risolvere 'T' perché' input' è una 'Lista ' quindi sa che 'T' è' int'. Quindi non può risolvere 'T2'. Vedi la mia risposta. – jason

+0

Non capisco come questa risposta abbia upvotes; è sbagliato. Non sta fallendo nel dedurre il tipo di un delegato. È che non è in grado di utilizzare un gruppo di metodi per dedurre uno dei parametri del tipo. – jason

+0

Il delegato richiede T2 come parametro generico e il compilatore non può inferire il tipo di T2 (in base alle specifiche, come si fa notare), ne consegue che il compilatore non può inferire il tipo del delegato. Anche se non è la causa principale - la tua spiegazione è chiaramente quella corretta - non è tecnicamente scorretta. – Dathan

0

Quanto segue funziona anche; Non so perché:

var outputs = Map(inputs, i => Square(i)); 
+2

Poiché a differenza dei gruppi metodo, le espressioni lambda svolgono un ruolo nell'inferenza dei parametri di tipo. cf. http://msdn.microsoft.com/en-us/library/ms364047(VS.80).aspx#cs3spec_topic4 – jason

1

C'era una po 'di scavo, ho trovato il vostro sospetto circa l'altra risposta è corretta. Ecco cosa dice la specifica C# 3.0:

7.4.2.1 - Per ciascuno degli argomenti del metodo Ei: Se Ei è un anonimo funzione o metodo di gruppo, un esplicito tipo parametro l'inferenza (7.4.2.7) è fatta ... 7.4.2.7 - ... Se e è una funzione anonima esplicitamente digitato con tipi di parametri U1 ... Uk e T è un tipo delegato con tipi di parametri V1 ... Vk allora per ogni Ui un'esatta inferenza (§7.4.2.8) è realizzato da Ui per il corrispondente Vi.

In altre parole, i metodi anonimi e gruppi di metodo (che Square è), si può dedurre solo i tipi di parametri esplicitamente. Penso che la giustificazione alla fine della risposta che hai citato riassuma bene. Poiché un'inferenza di tipo non può sempre essere resa implicitamente da un gruppo di metodi, il compilatore non la tenta nemmeno, secondo le specifiche.

1

Il motivo per cui questo non funziona è che C# esegue un'inferenza di tipo su un metodo, che deve conoscere il tipo di delegato all'altra estremità della conversione. Ma a questo punto, il tipo di delegato di destinazione non è ancora del tutto noto - solo T (int) è noto, T2 non è ancora risolto.

Func<int, int> f = Square; 
//works because we provided the destination type 
//of the conversion from Square to delegate 

Map(inputs, i => Square(i)); 
//works because the lambda follows the actual method call 
//and determines its own return type