2011-01-07 9 views
7

Cuando se le presente un d, podría tratarse de una secuencia fija como una lista o matriz, una AST que enumerará alguna fuente de datos externa, o incluso una AST en alguna colección existente. ¿Hay alguna forma de "materializar" con seguridad el enumerable para que las operaciones de enumeración como foreach, count, etc. no ejecuten el AST cada vez?¿Hay alguna manera de memorizar o materializar un IEnumerable?

A menudo he usado .ToArray() para crear esta representación, pero si el almacenamiento subyacente ya es una lista u otra secuencia fija, eso parece una copia desperdiciada. Sería bueno si pudiera hacerlo

var enumerable = someEnumerable.Materialize(); 

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

Sin tener que preocuparse de que .Any() y foreach intentar enumerar la secuencia dos veces y sin ella unccessarily copiar el enumerable.

+1

Esta es una buena idea, pero me gustaría señalar que a menudo, existingCollection.ToList se realiza para proteger contra las mutaciones de la colección existente. – Ani

+0

El problema con .ToList() es que creará una lista de enumerables que no son listas (arrays, ICollections, etc.) y devolverá una colección mutable. –

Respuesta

6

bastante fácil:

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

Este es un buen enfoque. Creo que sería mejor devolver un 'IEnumerable ' en su lugar, y también buscar 'ICollection' y' ICollection '. – Ani

+4

Esto es sutilmente diferente de la implementación Linq.ToList() que parece devolver siempre una copia nueva, por lo que los cambios en el resultado no cambian el original. Materializar según lo escrito, dependiendo del tipo de entrada, a veces devuelve una copia y, a veces, devuelve el original, por lo que los cambios en el resultado a veces cambian el original. – Handcraftsman

+0

Ani tiene la idea correcta. Mi intención no es crear una lista mutable, solo un 'IEnumerable ' que es seguro y eficiente para enumerar varias veces. Además, aunque nunca lo he probado, supongo que ToArray() es el materializador de respaldo más económico. –

2

Salida esta entrada del blog que escribí hace un par de años: http://www.fallingcanbedeadly.com/posts/crazy-extention-methods-tolazylist/

En él, definen una llamada ToLazyList método que efectivamente lo que estás buscando.

Como está escrito, eventualmente hará una copia completa de la secuencia de entrada, aunque podría modificarla para que las instancias de IList no se envuelvan en una LazyList, lo que evitaría que esto ocurra (esta acción, sin embargo, llevaría consigo la suposición de que cualquier IList que obtenga ya está efectivamente memorado).

+1

Esa es una extensión realmente interesante, pero no creo que esté relacionada con lo que quiere el OP. Eso * difiere * la materialización de la secuencia sobre una base de necesidad, mientras que el OP quiere * ansiosamente * materializar la secuencia de una manera eficiente; obteniendo una referencia a una colección existente si es necesario. – Ani

+0

Parece que algo está roto en tu blog. La URL que estaba en esta publicación que carece del 'www' redirigido a la raíz del sitio –

6

Igual que la respuesta de Thomas, sólo un poco mejor en mi opinión:

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

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

Tenga en cuenta que esto tiende a devolver la colección existente en sí si es una colección válida escriba o produzca una nueva colección de lo contrario. Si bien los dos son sutilmente diferentes, no creo que pueda ser un problema.

Cuestiones relacionadas