Cuente los elementos de un IEnumerable sin iterar?

private IEnumerable Tables { get { yield return "Foo"; yield return "Bar"; } } 

Digamos que quiero repetir eso y escribir algo así como procesar #n de #m.

¿Hay alguna manera de averiguar el valor de m sin iterar antes de mi iteración principal?

Espero haber sido claro.

IEnumerable no es compatible con esto. Esto es por diseño. IEnumerable usa evaluación diferida para obtener los elementos que solicita justo antes de que los necesite.

Si desea saber la cantidad de elementos sin iterar sobre ellos, puede usar ICollection , tiene una propiedad Count .

El método de extensión System.Linq.Enumerable.Count en IEnumerable tiene la siguiente implementación:

 ICollection c = source as ICollection; if (c != null) return c.Count; int result = 0; using (IEnumerator enumerator = source.GetEnumerator()) { while (enumerator.MoveNext()) result++; } return result; 

Por lo tanto, intenta ICollection a ICollection , que tiene una propiedad Count , y lo usa si es posible. De lo contrario, itera.

Por lo tanto, la mejor opción es utilizar el método de extensión Count() en su objeto IEnumerable , ya que obtendrá el mejor rendimiento posible de esa manera.

Solo añadiendo algo de información:

La extensión Count() no siempre itera. Considere Linq en Sql, donde el recuento va a la base de datos, pero en lugar de devolver todas las filas, emite el comando Sql Count() y en su lugar devuelve ese resultado.

Además, el comstackdor (o tiempo de ejecución) es lo suficientemente inteligente como para llamar al método Count() los objetos si tiene uno. Por lo tanto, no es lo que otros respondedores dicen, siendo completamente ignorantes y siempre iterando para contar los elementos.

En muchos casos, el progtwigdor simplemente está comprobando if( enumerable.Count != 0 ) utilizando el método de extensión Any() , como en if( enumerable.Any() ) es mucho más eficiente con la evaluación perezosa de linq, ya que puede provocar un cortocircuito una vez que puede determinar que hay elementos. También es más legible

IEnumerable no puede contar sin iterar.

En circunstancias “normales”, sería posible para las clases que implementan IEnumerable o IEnumerable , como List , implementar el método Count devolviendo la propiedad List .Count. Sin embargo, el método Count no es realmente un método definido en la interfaz IEnumerable o IEnumerable. (El único que, de hecho, es GetEnumerator). Y esto significa que no se puede proporcionar una implementación específica de clase.

Más bien, Count es un método de extensión, definido en la clase estática Enumerable. Esto significa que puede invocarse en cualquier instancia de una clase derivada de IEnumerable , independientemente de la implementación de esa clase. Pero también significa que se implementa en un solo lugar, externo a cualquiera de esas clases. Lo que, por supuesto, significa que debe implementarse de una manera que sea completamente independiente de estas partes internas de la clase. La única manera de hacerlo es a través de la iteración.

Un amigo mío tiene una serie de publicaciones en el blog que dan una ilustración de por qué no puedes hacer esto. Él crea una función que devuelve un IEnumerable donde cada iteración devuelve el siguiente número primo, todo el camino hasta ulong.MaxValue , y el siguiente elemento no se calcula hasta que lo solicite. Pregunta rápida y pop: ¿cuántos elementos se devuelven?

Aquí están las publicaciones, pero son algo largas:

  1. Beyond Loops (proporciona una clase inicial de EnumerableUtility utilizada en las otras publicaciones)
  2. Aplicaciones de Iterate (implementación inicial)
  3. Métodos de extensión loca: ToLazyList (optimizaciones de rendimiento)

No, no en general. Un punto en el uso de los enumerables es que el conjunto real de objetos en la enumeración no se conoce (de antemano, o incluso no aparece).

Puede usar System.Linq.

 using System; using System.Collections.Generic; using System.Linq; public class Test { private IEnumerable Tables { get { yield return "Foo"; yield return "Bar"; } } static void Main() { var x = new Test(); Console.WriteLine(x.Tables.Count()); } } 

Obtendrás el resultado ‘2’.

Alternativamente, puede hacer lo siguiente:

 Tables.ToList().Count; 

Yendo más allá de su pregunta inmediata (que ha sido completamente respondida negativamente), si está buscando informar el progreso mientras procesa un enumerable, es posible que desee consultar la publicación de mi blog Cómo informar el progreso durante las consultas de Linq .

Te deja hacer esto:

 BackgroundWorker worker = new BackgroundWorker(); worker.WorkerReportsProgress = true; worker.DoWork += (sender, e) => { // pretend we have a collection of // items to process var items = 1.To(1000); items .WithProgressReporting(progress => worker.ReportProgress(progress)) .ForEach(item => Thread.Sleep(10)); // simulate some real work }; 

Utilicé dicho método dentro de un método para verificar el contenido IEnumberable

 if( iEnum.Cast().Count() > 0) { } 

Dentro de un método como este:

 GetDataTable(IEnumberable iEnum) { if (iEnum != null && iEnum.Cast().Count() > 0) //--- proceed further } 

Depende de qué versión de .Net y la implementación de su objeto IEnumerable. Microsoft ha corregido el método IEnumerable.Count para comprobar la implementación y utiliza ICollection.Count o ICollection .Count, consulte los detalles aquí https://connect.microsoft.com/VisualStudio/feedback/details/454130

Y debajo está el MSIL de Ildasm para System.Core, en el que reside System.Linq.

 .method public hidebysig static int32 Count(class  [mscorlib]System.Collections.Generic.IEnumerable`1 source) cil managed { .custom instance void System.Runtime.CompilerServices.ExtensionAttribute::.ctor() = ( 01 00 00 00 ) // Code size 85 (0x55) .maxstack 2 .locals init (class [mscorlib]System.Collections.Generic.ICollection`1 V_0, class [mscorlib]System.Collections.ICollection V_1, int32 V_2, class [mscorlib]System.Collections.Generic.IEnumerator`1 V_3) IL_0000: ldarg.0 IL_0001: brtrue.s IL_000e IL_0003: ldstr "source" IL_0008: call class [mscorlib]System.Exception System.Linq.Error::ArgumentNull(string) IL_000d: throw IL_000e: ldarg.0 IL_000f: isinst class [mscorlib]System.Collections.Generic.ICollection`1 IL_0014: stloc.0 IL_0015: ldloc.0 IL_0016: brfalse.s IL_001f IL_0018: ldloc.0 IL_0019: callvirt instance int32 class [mscorlib]System.Collections.Generic.ICollection`1::get_Count() IL_001e: ret IL_001f: ldarg.0 IL_0020: isinst [mscorlib]System.Collections.ICollection IL_0025: stloc.1 IL_0026: ldloc.1 IL_0027: brfalse.s IL_0030 IL_0029: ldloc.1 IL_002a: callvirt instance int32 [mscorlib]System.Collections.ICollection::get_Count() IL_002f: ret IL_0030: ldc.i4.0 IL_0031: stloc.2 IL_0032: ldarg.0 IL_0033: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1 class [mscorlib]System.Collections.Generic.IEnumerable`1::GetEnumerator() IL_0038: stloc.3 .try { IL_0039: br.s IL_003f IL_003b: ldloc.2 IL_003c: ldc.i4.1 IL_003d: add.ovf IL_003e: stloc.2 IL_003f: ldloc.3 IL_0040: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext() IL_0045: brtrue.s IL_003b IL_0047: leave.s IL_0053 } // end .try finally { IL_0049: ldloc.3 IL_004a: brfalse.s IL_0052 IL_004c: ldloc.3 IL_004d: callvirt instance void [mscorlib]System.IDisposable::Dispose() IL_0052: endfinally } // end handler IL_0053: ldloc.2 IL_0054: ret } // end of method Enumerable::Count 

Aquí hay una gran discusión sobre evaluación diferida y ejecución diferida . Básicamente, tienes que materializar la lista para obtener ese valor.

El resultado de la función IEnumerable.Count () puede ser incorrecto. Esta es una muestra muy simple para probar:

 using System; using System.Collections.Generic; using System.Linq; using System.Collections; namespace Test { class Program { static void Main(string[] args) { var test = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }; var result = test.Split(7); int cnt = 0; foreach (IEnumerable chunk in result) { cnt = chunk.Count(); Console.WriteLine(cnt); } cnt = result.Count(); Console.WriteLine(cnt); Console.ReadLine(); } } static class LinqExt { public static IEnumerable> Split(this IEnumerable source, int chunkLength) { if (chunkLength <= 0) throw new ArgumentOutOfRangeException("chunkLength", "chunkLength must be greater than 0"); IEnumerable result = null; using (IEnumerator enumerator = source.GetEnumerator()) { while (enumerator.MoveNext()) { result = GetChunk(enumerator, chunkLength); yield return result; } } } static IEnumerable GetChunk(IEnumerator source, int chunkLength) { int x = chunkLength; do yield return source.Current; while (--x > 0 && source.MoveNext()); } } } 

El resultado debe ser (7,7,3,3) pero el resultado real es (7,7,3,17)

La única forma de contar rápidamente es cuando la colección original tiene un indexador (como una matriz). Para crear un código genérico con un requisito mínimo, puede usar IEnumerable, pero si necesita el conteo también, mi forma preferida es usar esta interfaz:

 public interface IEnumAndCount : IEnumerable { int Count { get; } } 

Si su colección original no tiene ningún indexador, su implementación de Count podría iterar sobre la colección, con el hit conocido en el rendimiento O (n).

Si no desea usar algo similar a IEnumAndCount, su mejor opción es ir con Linq.Count por las razones dadas por Daniel Earwicker cerca de la parte superior de esta pregunta.

Buena suerte !

No.

¿Ves esa información disponible en cualquier parte del código que hayas escrito?

Podría argumentar que el comstackdor puede “ver” que solo hay dos, pero eso significaría que necesitaría analizar cada método de iterador buscando solo ese caso patológico específico. E incluso si lo hiciera, ¿cómo lo leerías, dados los límites de un IEnumerable?

Sugeriría llamar a ToList. Sí, está haciendo la enumeración anticipadamente, pero aún tiene acceso a su lista de artículos.

Puede que no produzca el mejor rendimiento, pero puede usar LINQ para contar los elementos en un IEnumerable:

 public int GetEnumerableCount(IEnumerable Enumerable) { return (from object Item in Enumerable select Item).Count(); } 

Yo uso IEnum.ToArray().Length y funciona bien.

Uso dicho código, si tengo una lista de cadenas:

 ((IList)Table).Count