2010-10-19 16 views
8

Volevo scrivere un metodo di estensione che avrebbe funzionato su dizionari i cui valori erano una sorta di sequenza. Sfortunatamente, il compilatore non sembra inferire gli argomenti generici dal mio uso del metodo; Devo specificarli esplicitamente.Generici: perché il compilatore non può inferire gli argomenti del tipo in questo caso?

public static void SomeMethod<TKey, TUnderlyingValue, TValue> 
    (this IDictionary<TKey, TValue> dict) 
    where TValue : IEnumerable<TUnderlyingValue> { }  

static void Usage() 
{ 
    var dict = new Dictionary<int, string[]>(); 
    var dict2 = new Dictionary<int, IEnumerable<string>>(); 

    //These don't compile 
    dict.SomeMethod(); 
    SomeMethod(dict); // doesn't have anything to do with extension-methods 
    dict2.SomeMethod(); // hoped this would be easier to infer but no joy 


    //These work fine 
    dict.SomeMethod<int, string, string[]>(); 
    dict2.SomeMethod<int, string, IEnumerable<string>>(); 
} 

mi rendo conto che l'inferenza dei tipi non è una scienza esatta, ma mi chiedevo se c'è qualche fondamentale 'regola' che mi manca qui - io non sono a conoscenza dei dettagli della spec.

  1. Si tratta di una carenza del processo di deduzione o è la mia aspettativa che il compilatore debba "capirlo" in questo caso irragionevole (forse ambiguità)?
  2. Posso modificare la firma del metodo in un modo che lo renda ugualmente funzionale e tuttavia "inafferrabile"?
+0

Inferring TUnderlyingValue potrebbe essere difficile. Soprattutto perché il parametro type di 'IEnumerable <>' è covariante in .net 4. – CodesInChaos

+5

L'inferenza di tipo è una scienza esatta che ha algoritmi formali, non è solo un'ipotesi. Se un compilatore non è in grado di dedurre un tipo è solo perché non ha regole specifiche per gestire alcuni casi, e di solito è un progetto di implementazione, non un limite. Dai un'occhiata qui: http://www.classes.cs.uchicago.edu/archive/2005/winter/33600-1/slides/Lesson10.pdf – Jack

+0

@Jack: Grazie per il link; Interessante. Vedi il mio commento sulla risposta di Eric Lippert per quello che intendevo. Scarsa scelta di parole da parte mia, suppongo. – Ani

risposta

15

mi rendo conto che l'inferenza dei tipi non è una scienza esatta

non sono sicuro sono d'accordo. Le specifiche sono abbastanza dettagliate.

Mi chiedevo se c'è qualche fondamentale 'regola' che mi manca qui

La regola fondamentale che ti manca è probabilmente che i vincoli non sono parte della firma. L'inferenza del tipo funziona fuori dalla firma.

Sono a mio parere validi motivi per tale decisione di progettazione. Tuttavia, molte persone credono che io sia moralmente sbagliato nel credere che ci siano buone ragioni per quella decisione di progettazione. Se sei interessato a leggere ciò che sembra più di un milione di parole sull'argomento se ho ragione o torto, vedi il mio articolo sull'argomento e il centinaio di commenti che mi dicono che ho torto:

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

Si tratta di una carenza del processo di deduzione?

Probabilmente sì. A mio parere, è una scelta ragionevole data le esigenze di progettazione in competizione. (Quelli "fanno ciò che l'utente intende" e "danno errori quando le cose sembrano ambigui")

è la mia aspettativa che il compilatore dovrebbe "capire" irragionevole in questo caso?

No. Sembri una persona ragionevole e le tue aspettative sembrano essere basate su un buon ragionamento. Tuttavia, è del tutto possibile avere una ragionevole aspettativa che tuttavia non è soddisfatta. Questo sarebbe uno di quei casi.

Posso modificare la firma del metodo in un modo che lo renda ugualmente funzionale eppure "inafferrabile"?

Ciò sarà difficile, poiché il tipo di dizionario generico non è covariante o controverso nelle sue conversioni. Il concetto che si desidera acquisire non è facilmente espresso nel sistema di tipi in un modo che consente l'inferenza.

Se si preferisce utilizzare le lingue con inferenza di tipo più avanzata, considerare l'utilizzo di F #. Se preferisci le lingue che tendono a "fare ciò che l'utente intendeva" piuttosto che "segnalare errori di ambiguità", considera l'utilizzo di VB.

+0

Brillante, grazie - "i vincoli non fanno parte della firma" è quello che mi mancava, e devo girarmi attorno. In un'altra nota, ciò che intendevo per "non una scienza esatta" è che sarebbe possibile per due algoritmi di inferenza diversi ma validi (in generale, non specifici per le specifiche) per dedurre gli argomenti di tipo in modo diverso, tuttavia entrambi sono giustificati nella loro scelta. Sei d'accordo? – Ani

+0

@Ani: Assolutamente, ci sono molti tipi diversi di inferirer possibili. Ad esempio, abbiamo scelto deliberatamente di non utilizzare l'inferenza di tipo Hindley-Milner in C#. –

+0

Qual è il nome del tipo di inferenza che voi ragazzi avete usato? Mi chiedo solo se è inventato da qualcun altro come l'inferenza di tipo Hindley-Milner (presumo). –

1

Perché non tralasciare il tipo di IEnumerable?

public static void SomeMethod<TKey, TValue> 
(this IDictionary<TKey, TValue> dict) 
where TValue : IEnumerable { }  
+2

Perché in questo caso non sarà possibile accedere al tipo dei valori sottostanti (tutto ciò che si saprà è che ogni valore è una sequenza di * qualcosa *, ma non è ciò che è qualcosa. – AakashM

+0

Puoi trasmettere TValue a IEnumerable perché sai che è del tipo. –

4

L'inferenza di tipo C# non funziona a causa di vincoli o valori di ritorno. Così avrete leggermente migliore fortuna con

public static void SomeMethod<TKey, TUnderlyingValue> 
    (this IDictionary<TKey, IEnumerable<TUnderlyingValue>> dict) 
    { } 

Ciò funzionerà se si dichiara il parametro come new Dictionary< string, IEnumerable<int>>(), ma non se si dichiara che new Dictionary<string, List<int>>().

devo dire che il mio modo di leggere la sezione 7.5.2 della c# spec, sembra che da quando List<int> implementa IEnumerable<int>, l'inferenza di tipo di TUnderlyingValue dovrebbe funzionare. Tuttavia, quella sezione non è esattamente semplice da capire. Presumo che non funzioni attraverso i molteplici "layers", dal momento che SomeMethod<T>(IEnumberable<T> val){} funzionerebbe bene chiamandolo con SomeMethod(new List<string>()). Non vedo in modo specifico nulla nella specifica che si occupa della risoluzione di un tipo in cui U = Ca<Va, Cb<Vb>>, quindi l'inferenza forse a quel livello non è definita.

+0

+1 @Philip Rieck: Grazie. Sono a conoscenza di questa soluzione alternativa, ma non risolve il caso generale, che è quello che mi interessa. – Ani

+0

@Ani vedere le modifiche - Non sono sicuro che l'inferenza di tipo definita e implementata in C# 4.0 supporti quella livello di inferenza. –

+2

Questo non funzionerà con Dizionario > perché IDictionary è un tipo invariante. Supponiamo che il corpo del metodo lo contenga: 'dict.Add (new ObservableCollection ());'. Se esistesse un'interfaccia IReadOnlyDictionary di qualche tipo, potrebbe essere resa covariante sul parametro value e consentire a entrambi i dizionari di funzionare. –

Problemi correlati