Perché C # non deduce i miei tipi generici?

Sto avendo un sacco di divertimento Funy (destinato al divertimento) con metodi generici. Nella maggior parte dei casi l’inferenza di tipo C # è abbastanza intelligente da scoprire quali argomenti generici deve usare sui miei metodi generici, ma ora ho un progetto in cui il compilatore C # non ha successo, mentre credo che sia riuscito a trovare il tipi corretti.

Qualcuno può dirmi se il compilatore è un po ‘stupido in questo caso, o c’è una ragione molto chiara per cui non può dedurre i miei argomenti generici?

Ecco il codice:

Classi e definizioni di interfaccia:

interface IQuery { } interface IQueryProcessor { TResult Process(TQuery query) where TQuery : IQuery; } class SomeQuery : IQuery { } 

Qualche codice che non viene compilato:

 class Test { void Test(IQueryProcessor p) { var query = new SomeQuery(); // Does not compile :-( p.Process(query); // Must explicitly write all arguments p.Process(query); } } 

Perchè è questo? Cosa mi manca qui?

Ecco il messaggio di errore del compilatore (non lascia molto alla nostra immaginazione):

Gli argomenti di tipo per il metodo IQueryProcessor.Process (TQuery) non possono essere dedotti dall’utilizzo. Prova a specificare esplicitamente gli argomenti del tipo.

La ragione per cui credo che C # dovrebbe essere in grado di dedurlo è a causa di quanto segue:

  1. IQuery un object che implementa IQuery .
  2. Quella sola versione di IQuery che digita implementa è IQuery e quindi TResult deve essere una string .
  3. Con queste informazioni il compilatore ha TResult e TQuery.

SOLUZIONE

Per me la soluzione migliore è stata la modifica dell’interfaccia IQueryProcessor e l’utilizzo della digitazione dynamic nell’implementazione:

 public interface IQueryProcessor { TResult Process(IQuery query); } // Implementation sealed class QueryProcessor : IQueryProcessor { private readonly Container container; public QueryProcessor(Container container) { this.container = container; } public TResult Process(IQuery query) { var handlerType = typeof(IQueryHandler).MakeGenericType(query.GetType(), typeof(TResult)); dynamic handler = container.GetInstance(handlerType); return handler.Handle((dynamic)query); } } 

L’interfaccia IQueryProcessor ora IQuery un parametro IQuery . In questo modo può restituire un TResult e questo risolverà i problemi dal punto di vista del consumatore. È necessario utilizzare la riflessione nell’implementazione per ottenere l’implementazione effettiva, poiché sono necessari i tipi di query concreti (nel mio caso). Ma qui arriva la tipizzazione dynamic in soccorso che farà la riflessione per noi. Puoi leggere di più su questo in questo articolo .

Un gruppo di persone ha sottolineato che C # non fa inferenze basate su vincoli. Questo è corretto e pertinente alla domanda. Le inferenze vengono effettuate esaminando gli argomenti e i relativi tipi di parametri formali corrispondenti e questa è l’unica fonte di informazioni di inferenza.

Un gruppo di persone si è quindi collegato a questo articolo:

http://blogs.msdn.com/b/ericlippert/archive/2007/11/05/c-3-0-return-type-inference-does-not-work-on-member-groups.aspx

Questo articolo è obsoleto e irrilevante per la domanda. Non è aggiornato perché descrive una decisione di progettazione che abbiamo fatto in C # 3.0, che abbiamo poi invertito in C # 4.0, per lo più basato sulla risposta a quell’articolo. Ho appena aggiunto un aggiornamento in tal senso all’articolo.

È irrilevante poiché l’articolo riguarda l’ inferenza del tipo di ritorno dagli argomenti del gruppo metodo ai parametri formali delegati generici . Questa non è la situazione di cui il poster originale chiede.

Il mio articolo rilevante da leggere è piuttosto questo:

http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx

C # non inferirà tipi generici basati sul tipo restituito di un metodo generico, solo gli argomenti del metodo.

Inoltre, non utilizza i vincoli come parte dell’inferenza di tipo, che elimina il vincolo generico dal fornire il tipo per te.

Per i dettagli, vedi il post di Eric Lippert sull’argomento .

Non usa i vincoli per inferire i tipi. Piuttosto, ne deduce i tipi (quando ansible) e quindi controlla i vincoli.

Pertanto, mentre l’unico TResult ansible che potrebbe essere utilizzato con un parametro SomeQuery , non lo vedrà.

Si noti inoltre che sarebbe perfettamente ansible per SomeQuery implementare anche IQuery , che è una delle ragioni per cui questa limitazione sul compilatore potrebbe non essere una ctriggers idea.

Lo spec la definisce abbastanza chiaramente:

Sezione 7.4.2 Inferenza di tipo

Se il numero di argomenti fornito è diverso dal numero di parametri nel metodo, l’inferenza non riesce immediatamente. Altrimenti, supponi che il metodo generico abbia la seguente firma:

Tr M (T1 x1 … Tm xm)

Con una chiamata al metodo del modulo M (E1 … Em) l’attività di tipo inferenza consiste nel trovare argomenti di tipo unico S1 … Sn per ciascuno dei parametri tipo X1 … Xn in modo che la chiamata M (E1 … Em) diventi valida.

Come puoi vedere, il tipo di ritorno non è usato per l’inferenza di tipo. Se la chiamata al metodo non si associa direttamente all’argomento, l’inferenza degli argomenti fallisce immediatamente.

Il compilatore non presume semplicemente che tu volessi la string come argomento TResult , né può farlo. Immagina un TResult derivato dalla stringa. Entrambi sarebbero validi, quindi quale scegliere? Meglio essere espliciti.