¿Por qué IEnumerable no es devuelto por el método LINQ Where() totalmente consumido por Count()?

Yo era consciente de que el Count() El método proporcionado por LINQ tenía una optimización mediante la cual verificaría si la secuencia fuente implementada ICollection<T> y si es así llamar al Count propiedad en lugar de iterar sobre toda la colección. Cuando se utiliza esta optimización, el subyacente IEnumerable<T> no se consume, por lo que puede ser consumido por otras llamadas posteriores Count().

En particular, la sobrecarga de Count que acepta un predicado no realiza dicha optimización porque debe inspeccionar el valor de cada elemento.

Ahora considere el siguiente programa completo:

using System;
using System.Collections.Generic;
using System.Linq;

namespace count_where_issue
{
    class Program
    {
        static void Main(string[] args)
        {
            IEnumerable<int> items = new List<int> {1, 2, 3, 4, 5, 6};
            IEnumerable<int> evens = items.Where(y => y % 2 == 0);
            int count = evens.Count();
            int first = evens.First();
            Console.WriteLine("count = {0}", count);
            Console.WriteLine("first = {0}", first);
        }
    }
}

que imprime,

count = 3
first = 2

Mi expectativa era que el Conde necesitaría consumir todo el evens secuencia devuelta por Where() y la llamada posterior a evens.First() fallaría con InvalidOperationException porque la secuencia no contendría elementos.

¿Por qué este programa funciona de la manera que lo hace? Normalmente no intentaría usar un IEnumerable<T> siguiendo una llamada a `Count(). ¿Sería imprudente confiar en este comportamiento?

preguntado el 30 de junio de 12 a las 18:06

3 Respuestas

No sé si esto es lo que quieres decir, pero evens.Count() y evens.First() ambos enumeran el items.Where(...).

Puede ver esto en el resultado de lo siguiente:

class Program
{
    static void Main(string[] args)
    {
        IEnumerable<int> items = new List<int> { 1, 2, 3, 4, 5, 6 };
        IEnumerable<int> evens = items.Where(y => isEven(y));

        int count = evens.Count();
        Console.WriteLine("count = {0}", count);

        int first = evens.First();
        Console.WriteLine("first = {0}", first);

    }

    private static bool isEven(int y)
    {
        Console.Write(y + ": ");

        bool result = y % 2 == 0;

        Console.WriteLine(result);    
        return result;
    }
}

Qué es:

1: False
2: True
3: False
4: True
5: False
6: True
count = 3
1: False
2: True
first = 2

Respondido el 30 de junio de 12 a las 19:06

La altura de la cúpula es XNUMX metros, que es IEnumerator<T> no serías capaz de usar. Sin embargo, Count y First crean cada uno un enumerador separado (a través de llamadas a GetEnumerator al IEnumerable<T>).

Respondido el 30 de junio de 12 a las 19:06

Presumiblemente, esto solo es efectivo porque la última fuente de datos, la Lista, se puede repetir más de una vez. - Rob Smallshire

¿Qué le dio la impresión de que Count() afecta el IEnumerable original de alguna manera?

Los documentos no mencionan nada por el estilo...

http://msdn.microsoft.com/en-us/library/bb338038.aspx

Respondido el 30 de junio de 12 a las 19:06

Tuve la impresión de leer las publicaciones del blog de Jon Skeet sobre el funcionamiento de LINQ, en particular este artículo: msmvps.com/blogs/jon_skeet/archive/2010/12/26/… - Rob Smallshire

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.