2012-02-25 35 views
28

ho scritto questo metodo di estensione (che compila):Flatten IEnumerable <IEnumerable <>>; comprensione generici

public static IEnumerable<J> Flatten<T, J>(this IEnumerable<T> @this) 
              where T : IEnumerable<J> 
{ 
    foreach (T t in @this) 
     foreach (J j in t) 
      yield return j; 
} 

Il codice sotto causa un errore di compilazione (nessun metodo adatto trovato), perché?:

IEnumerable<IEnumerable<int>> foo = new int[2][]; 
var bar = foo.Flatten(); 

Se a implementare l'estensione come qui di seguito, ottengo nessun errore di compilazione:

public static IEnumerable<J> Flatten<J>(this IEnumerable<IEnumerable<J>> @this) 
{ 
    foreach (IEnumerable<J> js in @this) 
     foreach (J j in js) 
      yield return j; 
} 

Edit (2): Questa domanda mi considero risposto, ma ha sollevato un'altra domanda per quanto riguarda risoluzione di sovraccarico e vincoli di tipo. Questa domanda che ho messo qui: Why aren't type constraints part of the method signature?

+1

La tua modifica non funziona perché ne hai troppe enumerabili. 'foo.Flatten , int>();' dovrebbe funzionare. – dlev

risposta

65

In primo luogo, non è necessario Flatten(); quel metodo esiste già e si chiama SelectMany(). Si può usare in questo modo:

IEnumerable<IEnumerable<int>> foo = new [] { new[] {1, 2}, new[] {3, 4} }; 
var bar = foo.SelectMany(x => x); // bar is {1, 2, 3, 4} 

In secondo luogo, il primo tentativo non funziona perché l'inferenza di tipo generico funziona basato solo sugli argomenti al metodo, vincoli non generici associati al metodo. Poiché non esiste alcun argomento che utilizzi direttamente il parametro generico J, il motore di inferenza del tipo non può indovinare cosa dovrebbe essere J e quindi non pensa che il metodo sia un candidato.

È edificante vedere come SelectMany() aggira questo: richiede un ulteriore argomento Func<TSource, TResult>. Ciò consente al motore di inferenza del tipo di determinare entrambi i tipi generici, poiché entrambi sono disponibili basandosi esclusivamente sugli argomenti forniti per il metodo.

+1

@Daryl: Perché dovrebbe essere 'Flatten , int> (foo)' – BrokenGlass

+2

@Daryl I vincoli generici non sono considerati parte della firma di un metodo; per * molto altro *, vedi questo link: http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx – dlev

+1

@Daryl : No - c'è sicuramente una curva di apprendimento qui, questo non è di gran lunga l'aspetto più semplice di C# da capire.Il solo tentativo di dominarlo ti mette già davanti al 95% del resto ;-) – BrokenGlass

13

la risposta di dlev va bene; Ho solo pensato di aggiungere un po 'più di informazioni.

In particolare, noto che si sta tentando di utilizzare i generici per implementare una sorta di covarianza su IEnumerable<T>. In C# 4 e versioni successive, IEnumerable<T> è già covariante.

Il tuo secondo esempio illustra questo. Se si dispone di

List<List<int>> lists = whatever; 
foreach(int x in lists.Flatten()) { ... } 

quindi digitare l'inferenza si ragiona che List<List<int>> è convertibile in IE<List<int>>, List<int> è convertibile in , e, quindi, a causa della covarianza, IE<List<int>> è convertibile in IE<IE<int>>. Ciò conferisce al concetto di inferenza qualcosa su cui andare avanti; può dedurre che T è int, e tutto è buono.

Questo non funziona in C# 3. La vita è un po 'più difficile in un mondo senza covarianza ma si può ottenere con un uso giudizioso del metodo di estensione Cast<T>.

Problemi correlati