2011-01-07 14 views
7

Quando viene fornito uno d si potrebbe avere a che fare con una sequenza fissa come un elenco o array, un AST che enumera qualche origine dati esterna, o anche un AST su alcune raccolte esistenti. Esiste un modo per "materializzare" in modo sicuro l'enumerabile in modo che le operazioni di enumerazione come foreach, count, ecc non eseguano l'AST ogni volta?Esiste un modo per memorizzare o materializzare un oggetto IEnumerable?

Ho spesso utilizzato .ToArray() per creare questa rappresentazione, ma se la memoria sottostante è già una lista o un'altra sequenza fissa, sembra una copia sprecata. Sarebbe bello se potessi fare

var enumerable = someEnumerable.Materialize(); 

if(enumberable.Any() { 
    foreach(var item in enumerable) { 
    ... 
    } 
} else { 
    ... 
} 

senza doversi preoccupare che .Any() e foreach cercare di enumerare la sequenza di due volte e senza di essa unccessarily copiare il enumerabile.

+1

Questa è una buona idea, ma vorrei sottolineare che spesso, existingCollection.ToList è fatto per proteggere contro le mutazioni alla collezione esistente. – Ani

+0

Il problema con .ToList() è che creerà un elenco di enumerabili che non sono elenchi (array, ICollections, ecc.) E restituiscono una raccolta mutabile. –

risposta

6

Abbastanza facile:

public static IList<TSource> Materialize<TSource>(this IEnumerable<TSource> source) 
{ 
    if (source is IList<TSource>) 
    { 
     // Already a list, use it as is 
     return (IList<TSource>)source; 
    } 
    else 
    { 
     // Not a list, materialize it to a list 
     return source.ToList(); 
    } 
} 
+3

Questo è un buon approccio. Penso che sarebbe meglio restituire un 'IEnumerable ', e anche controllare 'ICollection' e' ICollection '. – Ani

+4

Questo è leggermente diverso dall'implementazione Linq.ToList() che sembra restituire sempre una nuova copia in modo che le modifiche al risultato non cambino l'originale. Materializza come sarà scritto, a seconda del tipo di input, a volte restituisce una copia e talvolta restituisce l'originale - quindi le modifiche al risultato a volte cambiano l'originale. – Handcraftsman

+0

Ani ha avuto l'idea giusta. La mia intenzione non è quella di creare una lista mutabile, solo un 'IEnumerable ' che sia sicuro ed efficiente per enumerare più volte. Inoltre, mentre non l'ho mai testato, presumo che ToArray() sia il materializzatore di fallback più economico. –

2

Partenza questo post del blog ho scritto un paio di anni fa: http://www.fallingcanbedeadly.com/posts/crazy-extention-methods-tolazylist/

In esso, ho definire un metodo chiamato ToLazyList che fa effettivamente quello che stai cercando.

Come scritto, alla fine eseguirà una copia completa della sequenza di input, sebbene sia possibile modificarla in modo che le istanze di IList non vengano incapsulate in una LazyList, il che impedirebbe che ciò accada (tuttavia, questa azione porterebbe con sé l'ipotesi che qualsiasi IIL che ottieni sia già effettivamente memo- rizzato).

+1

Questa è un'estensione davvero interessante, ma non penso sia correlata a ciò che l'OP vuole. Ciò * rinvia * la materializzazione della sequenza su una base di necessità, mentre l'OP vuole * ardire * materializzare la sequenza in modo efficiente; ottenere un riferimento a una collezione esistente, se necessario. – Ani

+0

Sembra che qualcosa sia rotto sul tuo blog. L'URL che era su questo post privo del 'www' reindirizzato alla radice del sito –

6

Stessa risposta di Thomas, solo un po 'meglio secondo me:

public static ICollection<T> Materialize<T>(this IEnumerable<T> source) 
{ 
    if (source == null) 
     return null; 

    return source as ICollection<T> ?? source.ToList(); 
} 

Si prega di notare che questo tendono a tornare alla collezione esistente in sé se una collezione valida digita o genera una nuova raccolta altrimenti. Mentre i due sono sottilmente diversi, non penso che potrebbe essere un problema.

Problemi correlati