Domanda Appiattisci IEnumerable >; capire i generici


Ho scritto questo metodo di estensione (che compila):

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 riportato di seguito causa un errore di compilazione (non è stato trovato alcun metodo adatto), perché?:

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

Se implemento l'estensione come di seguito, non ottengo alcun errore in fase 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, a mio avviso, ha risposto, ma ha sollevato un'altra domanda riguardante la risoluzione del sovraccarico e i vincoli di tipo. Questa domanda ho messo qui: Perché i vincoli di tipo non fanno parte della firma del metodo?


29
2018-02-25 02:02


origine


risposte:


Innanzitutto, non è necessario Flatten(); quel metodo esiste già e viene chiamato SelectMany(). Puoi usarlo 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 solo in base agli argomenti del metodo, non ai vincoli generici associati al metodo. Dal momento che non ci sono argomenti che utilizzano direttamente il J parametro generico, il motore di inferenza del tipo non può indovinare cosa J dovrebbe essere, e quindi non pensa che il tuo metodo sia un candidato.

È edificante vedere come SelectMany() aggira questo: richiede un ulteriore Func<TSource, TResult> discussione. 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.


68
2018-02-25 02:09



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

Nello specifico, noto che stai tentando di utilizzare i generici per implementare una sorta di covarianza IEnumerable<T>. In C # 4 e sopra, IEnumerable<T> è già covariante. 

Il tuo secondo esempio illustra questo. Se hai

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

quindi digitare l'inferenza lo ragionerà List<List<int>> è convertibile in IE<List<int>>, List<int> è convertibile in IE<int>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 puoi cavartela con un uso giudizioso del Cast<T> metodo di estensione.


14
2018-02-25 05:17