Domanda

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

Diciamo che voglio iterare su quelli e scrivere qualcosa come l'elaborazione #n di #m.

Esiste un modo per scoprire il valore di m senza iterare prima della mia iterazione principale?

Spero di essermi chiarito.

È stato utile?

Soluzione

IEnumerable non supporta questo. Questo è di progettazione. IEnumerable utilizza la valutazione lazy per ottenere gli elementi richiesti appena prima di averne bisogno.

Se vuoi conoscere il numero di elementi senza iterare su di essi puoi usare ICollection < T > , ha una proprietà Count .

Altri suggerimenti

Il metodo di estensione System.Linq.Enumerable.Count su IEnumerable < T > ha la seguente implementazione:

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;

Quindi tenta di eseguire il cast su ICollection < T > , che ha una proprietà Count e la usa se possibile. Altrimenti itera.

Quindi la soluzione migliore è utilizzare il metodo di estensione Count () sull'oggetto IEnumerable < T > , poiché otterrai le migliori prestazioni possibili in questo modo.

Aggiungendo solo alcune informazioni extra:

L'estensione Count () non scorre sempre. Si consideri Linq a Sql, dove il conteggio va al database, ma invece di riportare tutte le righe, emette il comando Sql Count () e restituisce invece quel risultato.

Inoltre, il compilatore (o runtime) è abbastanza intelligente da chiamare il metodo Count () degli oggetti se ne ha uno. Quindi è non come dicono gli altri rispondenti, essendo completamente ignoranti e sempre iteranti per contare gli elementi.

In molti casi in cui il programmatore sta solo controllando if (enumerable.Count! = 0) utilizzando il metodo di estensione Any () , come in if ( enumerable.Any ()) è molto più efficiente con la valutazione pigra di linq in quanto può cortocircuitare una volta che può determinare che ci sono elementi. È anche più leggibile

Un mio amico ha una serie di post sul blog che forniscono un'illustrazione del perché non puoi farlo. Crea una funzione che restituisce un IEnumerable in cui ogni iterazione restituisce il numero primo successivo, fino a ulong.MaxValue , e l'elemento successivo non viene calcolato fino a quando non lo chiedi. Domanda rapida e pop: quanti articoli vengono restituiti?

Ecco i post, ma sono piuttosto lunghi:

  1. Beyond Loops (fornisce una classe EnumerableUtility iniziale utilizzata negli altri post)
  2. Applicazioni di Iterate (Implementazione iniziale)
  3. Metodi di estensione pazza: ToLazyList (ottimizzazioni delle prestazioni)

IEnumerable non può contare senza iterare.

In " normale " circostanze, sarebbe possibile per le classi che implementano IEnumerable o IEnumerable < T > ;, come List < T > ;, implementare il metodo Count restituendo la proprietà List < T > .Count. Tuttavia, il metodo Count non è in realtà un metodo definito su IEnumerable < T > o interfaccia IEnumerable. (L'unico che in realtà è GetEnumerator). Ciò significa che non è possibile fornire un'implementazione specifica per classe.

Piuttosto, Count è un metodo di estensione, definito sulla classe statica Enumerable. Ciò significa che può essere chiamato in qualsiasi istanza di un IEnumerable < T > classe derivata, indipendentemente dall'implementazione di quella classe. Ma significa anche che è implementato in un unico posto, esterno a una di quelle classi. Il che ovviamente significa che deve essere implementato in modo completamente indipendente dagli interni di queste classi. L'unico modo per contare è tramite iterazione.

In alternativa puoi fare quanto segue:

Tables.ToList<string>().Count;

No, non in generale. Un punto nell'uso degli enumerabili è che l'insieme effettivo di oggetti nell'enumerazione non è noto (in anticipo o addirittura affatto).

Puoi usare 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());
    }
}

Otterrai il risultato '2'.

Andando oltre la tua domanda immediata (a cui è stata data una risposta esauriente in negativo), se stai cercando di segnalare i progressi durante l'elaborazione di un enumerabile, potresti voler guardare il mio post sul blog Avanzamento dei report durante le query Linq .

Ti consente di farlo:

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

Ho usato in questo modo un metodo per verificare il contenuto IEnumerable passato

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

}

All'interno di un metodo come questo:

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

}

Dipende dalla versione di .Net e dall'implementazione dell'oggetto IEnumerable. Microsoft ha corretto il metodo IEnumerable.Count per verificare l'implementazione e utilizza ICollection.Count o ICollection < TSource > .Count, vedi i dettagli qui https://connect.microsoft.com/VisualStudio/ feedback / dettagli / 454130

E sotto c'è MSIL di Ildasm per System.Core, in cui risiede 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

Ecco una grande discussione su valutazione pigra e esecuzione differita . Fondamentalmente devi materializzare l'elenco per ottenere quel valore.

Il risultato della funzione IEnumerable.Count () potrebbe essere errato. Questo è un esempio molto semplice da testare:

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

Il risultato deve essere (7,7,3,3) ma il risultato effettivo è (7,7,3,17)

L'unico modo per avere un conteggio veloce è quando la collezione originale ha un indicizzatore (come un array). Per creare un codice generico con un requisito minimo, puoi usare IEnumerable ma se hai bisogno del conteggio, allora il mio modo preferito è usare questa interfaccia:


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

Se la tua raccolta originale non ha alcun indicizzatore, l'implementazione di Count potrebbe scorrere sulla raccolta, con l'hit noto nelle prestazioni O (n).

Se non si desidera utilizzare qualcosa di simile a IEnumAndCount, la soluzione migliore è quella di utilizzare Linq.Count per i motivi forniti da Daniel Earwicker nella parte superiore di questa domanda.

Buona fortuna!

No.

Le informazioni sono disponibili ovunque nel codice che hai scritto?

Potresti sostenere che il compilatore può " vedi " che ce ne sono solo due, ma ciò significherebbe che sarebbe necessario analizzare ogni metodo iteratore cercando solo quel caso patologico specifico. E anche se lo facesse, come lo leggeresti, dati i limiti di un IEnumerable?

Suggerirei di chiamare ToList. Sì, stai eseguendo l'enumerazione in anticipo, ma hai ancora accesso al tuo elenco di elementi.

Potrebbe non offrire le migliori prestazioni, ma puoi usare LINQ per contare gli elementi in un IEnumerable:

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

Uso IEnum < string > .ToArray < string > (). Lunghezza e funziona bene.

Uso tale codice, se ho un elenco di stringhe:

((IList<string>)Table).Count
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top