2011-09-30 29 views
9

Digamos que tengo un poco de código:lectura IEnumerable varias veces

var items = ItemsGetter.GetAllItems().Where(x => x.SomeProperty > 20); 
int sum1 = items.Sum(x => x.SomeFlag == true); 

Y por ejemplo necesito alguna otra suma de la colección de artículos más adelante en el código.

int sum2 = items.Sum(x => x.OtherFlag == false); 

Así que mi pregunta: ¿está bien llamar a los métodos de Linq en IEnumerable más de una vez? ¿Tal vez debería llamar al método Reset() en el enumerador o hacer una lista de elementos usando el método ToList?

Respuesta

16

Bueno, realmente depende de lo que quieras hacer. Usted podría tener el éxito de la ejecución de la consulta dos veces (y el significado exacto de que va a depender de lo que GetAllItems() hace), o que podría recibir el golpe de copiar los resultados a una lista:

var items = ItemsGetter.GetAllItems().Where(x => x.SomeProperty > 20).ToList(); 

Una vez que está en una lista, obviamente no es un problema iterar sobre esa lista varias veces.

Tenga en cuenta que no puede llamar al Reset porque no tiene el iterador; tiene el IEnumerable<T>. No recomendaría llamar al IEnumerator<T> en general de todos modos, muchas implementaciones (incluidas las generadas por el compilador de C# de los bloques de iteradores) en realidad no implementan Reset (es decir, lanzan una excepción).

1

LINQ utiliza la ejecución diferida, por lo que los 'elementos' solo se enumerarán cuando lo solicite a través de otro método. Cada uno de los métodos de Su suma tomará O (n) para iterar. Dependiendo de qué tan grande sea su lista de elementos, es posible que no desee repetirla varias veces.

+0

esto no es una responder a la pregunta, que era "¿Está bien llamar a los métodos de Linq en IEnumerable más de una vez?" ... la respuesta es no". –

+0

Bueno, lo que "OK" significa es un poco confuso. Puede iterar sobre una lista cuantas veces quiera. Menciono las ramificaciones de esto en mi respuesta. ¿Cómo no está bien iterar sobre ellos? Publique su propia respuesta si lo cree (aunque ya hay una respuesta aceptada). –

+0

Está perfectamente claro. 'items' es un IEnumerable, no una lista, y solo puede atravesarlo una vez. No es necesario publicar otra respuesta, pero es importante que los lectores entiendan que esta es incorrecta y que ni siquiera toca la pregunta. –

3

Ocasionalmente me encuentro en la situación de tener que procesar un enumerable varias veces. Si la enumeración es costosa, no repetible y arroja una gran cantidad de datos (como un IQueryable que se lee desde una base de datos), enumerar varias veces no es una opción, ni almacenar el resultado en la memoria.

Hasta hoy a menudo terminaba escribiendo clases de agregador en las que podía insertar elementos en un ciclo foreach y eventualmente leer los resultados, mucho menos elegante que LINQ.

Pero espera, acabo de decir "push"? ¿No suena como ... reactivo? Así que estaba pensando en la caminata de esta noche. De vuelta a casa, lo probé, ¡y funciona!

El fragmento de ejemplo muestra cómo obtener tanto el artículo en su mínimo y máximo de una secuencia de números enteros en una sola pasada, utilizando operadores LINQ estándar (las de Rx, que es):

public static MinMax GetMinMax(IEnumerable<int> source) 
{ 
    // convert source to an observable that does not enumerate (yet) when subscribed to 
    var connectable = source.ToObservable(Scheduler.Immediate).Publish(); 

    // set up multiple consumers 
    var minimum = connectable.Min(); 
    var maximum = connectable.Max(); 

    // combine into final result 
    var final = minimum.CombineLatest(maximum, (min, max) => new MinMax { Min = min, Max = max }); 

    // make final subscribe to consumers, which in turn subscribe to the connectable observable 
    var resultAsync = final.GetAwaiter(); 

    // now that everybody is listening, enumerate! 
    connectable.Connect(); 

    // result available now 
    return resultAsync.GetResult(); 
}