Pregunta

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

Digamos que quiero iterar en esos y escribir algo como procesar #n de #m.

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

Espero haberme aclarado.

¿Fue útil?

Solución

IEnumerable no admite esto. Esto es por diseño. IEnumerable utiliza la evaluación perezosa para obtener los elementos que solicita justo antes de que los necesite.

Si desea saber la cantidad de elementos sin iterar sobre ellos, puede utilizar ICollection < T > , tiene una propiedad de Count .

Otros consejos

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

ICollection<T> c = source as ICollection<TSource>;
if (c != null)
    return c.Count;

int result = 0;
using (IEnumerator<T> enumerator = source.GetEnumerator())
{
    while (enumerator.MoveNext())
        result++;
}
return result;

Por lo tanto, intenta convertir a ICollection < T > , que tiene una propiedad de Count , y la utiliza si es posible. De lo contrario, itera.

Entonces, lo mejor es utilizar el método de extensión Count () en su objeto IEnumerable < T > , ya que obtendrá el mejor rendimiento posible de esa manera.

Solo añadiendo información extra:

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

Además, el compilador (o tiempo de ejecución) es lo suficientemente inteligente como para llamar al método Count () de los objetos si tiene uno. Así que no es no como dicen otros respondedores, ser completamente ignorante y siempre iterar para contar elementos.

En muchos casos, el programador solo está verificando 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 si hay algún elemento. También es más legible

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

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

  1. Beyond Loops (proporciona una clase inicial de EnumerableUtility utilizada en las otras publicaciones)
  2. Aplicaciones de Iterate (implementación inicial)
  3. Crazy Extention Methods: ToLazyList (Optimizaciones de rendimiento)

IEnumerable no puede contar sin iterar.

En " normal " En algunas circunstancias, sería posible que las clases que implementan IEnumerable o IEnumerable < T > ;, como List < T > ;, implementen el método Count devolviendo la propiedad List < T > .Count. Sin embargo, el método Count no es realmente un método definido en IEnumerable < T > o interfaz IEnumerable. (El único que es, de hecho, es GetEnumerator). Y esto significa que no se le 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 se puede llamar en cualquier instancia de un IEnumerable < T > clase derivada, 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 los aspectos internos de esta clase. La única manera de hacer el recuento es a través de la iteración.

Alternativamente, puedes hacer lo siguiente:

Tables.ToList<string>().Count;

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

Puedes usar System.Linq.

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

public class Test
{
    private IEnumerable<string> 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'.

Si va más allá de su pregunta inmediata (que ha sido completamente respondida negativamente), si desea informar sobre el progreso mientras procesa un enumerable, puede consultar la publicación de mi blog Informe de progreso durante las consultas de Linq .

Te permite 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
      };

Lo utilicé de esta manera dentro de un método para verificar el contenido de IEnumerable

if( iEnum.Cast<Object>().Count() > 0) 
{

}

Dentro de un método como este:

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

}

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

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

.method public hidebysig static int32  Count<TSource>(class 

[mscorlib]System.Collections.Generic.IEnumerable`1<!!TSource> 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<!!TSource> V_0,
           class [mscorlib]System.Collections.ICollection V_1,
           int32 V_2,
           class [mscorlib]System.Collections.Generic.IEnumerator`1<!!TSource> 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<!!TSource>
  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<!!TSource>::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<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<!!TSource>::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 perezosa 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<int> chunk in result)
      {
        cnt = chunk.Count();
        Console.WriteLine(cnt);
      }
      cnt = result.Count();
      Console.WriteLine(cnt);
      Console.ReadLine();
    }
  }

  static class LinqExt
  {
    public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int chunkLength)
    {
      if (chunkLength <= 0)
        throw new ArgumentOutOfRangeException("chunkLength", "chunkLength must be greater than 0");

      IEnumerable<T> result = null;
      using (IEnumerator<T> enumerator = source.GetEnumerator())
      {
        while (enumerator.MoveNext())
        {
          result = GetChunk(enumerator, chunkLength);
          yield return result;
        }
      }
    }

    static IEnumerable<T> GetChunk<T>(IEnumerator<T> 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 tener un recuento rápido es cuando la colección original tiene un indizador (como matriz). Para crear un código genérico con un requisito mínimo, puede usar IEnumerable, pero si también necesita el conteo, entonces mi forma preferida es usar esta interfaz:


    public interface IEnumAndCount<out T> : IEnumerable<T>
    {
        int Count { get; }
    }

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

Si no quieres usar algo similar a IEnumAndCount, lo mejor que puedes hacer es ir con Linq.Count por razones dadas por Daniel Earwicker cerca de la parte superior de esta pregunta.

¡Buena suerte!

No.

¿Ves esa información disponible en algún lugar del código que has escrito?

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

Yo sugeriría llamar a ToList. Sí, está haciendo la enumeración antes, pero aún tiene acceso a su lista de elementos.

Es posible 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();
}

Uso IEnum < string > .ToArray < string > (). Length y funciona bien.

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

((IList<string>)Table).Count
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top