it-swarm.com.de

Zählen Sie die Elemente aus einer IEnumerable <T> ohne Iteration?

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

Angenommen, ich möchte diese iterieren und so etwas wie "processing #n of #m" schreiben.

Gibt es eine Möglichkeit, den Wert von m herauszufinden, ohne vor meiner Hauptiteration zu iterieren?

Ich hoffe, ich habe mich klar ausgedrückt.

298
sebagomez

IEnumerable unterstützt dies nicht. Dies ist beabsichtigt. IEnumerable verwendet eine verzögerte Auswertung, um die gewünschten Elemente zu erhalten, bevor Sie sie benötigen.

Wenn Sie die Anzahl der Elemente kennen möchten, ohne sie zu durchlaufen, können Sie ICollection<T>, es hat eine Count -Eigenschaft.

314
Mendelt

Die Erweiterungsmethode System.Linq.Enumerable.Count Für IEnumerable<T> Weist die folgende Implementierung auf:

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;

Es wird also versucht, nach ICollection<T> Umzuwandeln, das eine Count -Eigenschaft hat, und wenn möglich verwendet. Ansonsten wird iteriert.

Verwenden Sie am besten die Erweiterungsmethode Count() für Ihr IEnumerable<T> - Objekt, da Sie auf diese Weise die bestmögliche Leistung erzielen.

204

Füge einfach ein paar zusätzliche Informationen hinzu:

Die Count() -Erweiterung iteriert nicht immer. Betrachten Sie Linq to Sql, wo die Anzahl an die Datenbank geht, aber anstatt alle Zeilen zurückzubringen, gibt es den Befehl Sql Count() aus und gibt stattdessen dieses Ergebnis zurück.

Darüber hinaus ist der Compiler (oder die Laufzeit) so intelligent, dass er die Methode objects Count() aufruft, wenn er eine hat. Also ist es nicht, wie andere Responder sagen, völlig ignorant zu sein und immer zu iterieren, um Elemente zu zählen.

In vielen Fällen, in denen der Programmierer nur if( enumerable.Count != 0 ) mit der Erweiterungsmethode Any() prüft, wie in if( enumerable.Any() ), ist die verzögerte Auswertung von linq weitaus effizienter, da sie Schaltung, sobald es feststellen kann, gibt es irgendwelche Elemente. Es ist auch besser lesbar

86
Robert Paulson

Ein Freund von mir hat eine Reihe von Blog-Posts, die veranschaulichen, warum dies nicht möglich ist. Er erstellt eine Funktion, die eine IEnumerable zurückgibt, wobei jede Iteration die nächste Primzahl zurückgibt, bis hin zu ulong.MaxValue, und der nächste Artikel wird erst berechnet, wenn Sie danach fragen. Schnelle Pop-Frage: Wie viele Artikel werden zurückgesandt?

Hier sind die Posts, aber sie sind ziemlich lang:

  1. Beyond Loops (stellt eine anfängliche EnumerableUtility-Klasse bereit, die in den anderen Posts verwendet wird)
  2. Anwendungen von Iterate (Erstimplementierung)
  3. Crazy Extention Methods: ToLazyList (Leistungsoptimierungen)
12
Joel Coehoorn

IEnumerable kann ohne Iteration nicht zählen.

Unter "normalen" Umständen können Klassen, die IEnumerable oder IEnumerable <T> implementieren, wie z. B. List <T>, die Count-Methode implementieren, indem sie die List <T> .Count-Eigenschaft zurückgeben. Die Count-Methode ist jedoch keine in der IEnumerable <T> - oder IEnumerable-Schnittstelle definierte Methode. (Das einzige, das tatsächlich ist, ist GetEnumerator.) Dies bedeutet, dass keine klassenspezifische Implementierung dafür bereitgestellt werden kann.

Count ist eine Erweiterungsmethode, die für die statische Klasse Enumerable definiert ist. Dies bedeutet, dass es für jede Instanz einer von IEnumerable <T> abgeleiteten Klasse aufgerufen werden kann, unabhängig von der Implementierung dieser Klasse. Es bedeutet aber auch, dass es an einem einzigen Ort außerhalb einer dieser Klassen implementiert wird. Das bedeutet natürlich, dass es auf eine Art und Weise implementiert werden muss, die völlig unabhängig von den Interna dieser Klasse ist. Die einzige Möglichkeit zum Zählen ist die Iteration.

10
Chris Ammerman

Alternativ können Sie Folgendes tun:

Tables.ToList<string>().Count;
9
h_alsharaf

Nein, im Allgemeinen nicht. Ein Punkt bei der Verwendung von Aufzählungen ist, dass die tatsächliche Menge von Objekten in der Aufzählung nicht bekannt ist (im Voraus oder überhaupt nicht).

8
JesperE

Sie können System.Linq verwenden.

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

Sie erhalten das Ergebnis '2'.

8
prosseek

Wenn Sie über Ihre unmittelbare Frage hinausgehen (die gründlich verneint wurde) und während der Verarbeitung einer Aufzählung über Fortschritte berichten möchten, sollten Sie sich meinen Blog-Beitrag ansehen Berichterstattung über Fortschritte bei Linq-Abfragen .

Damit können Sie Folgendes tun:

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
      };
5
Samuel Jack

Ich habe diese Methode innerhalb einer Methode verwendet, um den übergebenen IEnumberable -Inhalt zu überprüfen

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

}

In einer Methode wie dieser:

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

}
4
Shahidul Haque

Es hängt von der Version von .Net und der Implementierung Ihres IEnumerable-Objekts ab. Microsoft hat die IEnumerable.Count-Methode behoben, um die Implementierung zu überprüfen, und verwendet ICollection.Count oder ICollection <TSource> .Count. Weitere Informationen finden Sie hier https://connect.Microsoft.com/VisualStudio/feedback/details/4541

Und unten ist die MSIL von Ildasm für System.Core, in der sich das System.Linq befindet.

.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
3
prabug

Ergebnis der IEnumerable.Count () - Funktion ist möglicherweise falsch. Dies ist ein sehr einfaches Beispiel zum Testen:

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

Das Ergebnis muss (7,7,3,3) sein, aber das tatsächliche Ergebnis ist (7,7,3,17).

2
Roman Golubin

Hier ist eine großartige Diskussion über verzögerte Auswertung und verzögerte Ausführung . Grundsätzlich müssen Sie die Liste materialisieren, um diesen Wert zu erhalten.

2
JP Alioto

Die einzige Möglichkeit, schnell zu zählen, besteht darin, dass die ursprüngliche Auflistung über einen Indexer (wie ein Array) verfügt. Um generischen Code mit einer Mindestanforderung zu erstellen, können Sie IEnumerable verwenden. Wenn Sie jedoch auch die Anzahl benötigen, ist meine bevorzugte Methode die Verwendung dieser Schnittstelle:


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

Wenn Ihre ursprüngliche Auflistung keinen Indexer enthält, kann Ihre Count-Implementierung die Auflistung mit dem bekannten Leistungsverlust O (n) durchlaufen.

Wenn Sie etwas Ähnliches wie IEnumAndCount nicht verwenden möchten, entscheiden Sie sich am besten für Linq.Count, und zwar aus Gründen, die Daniel Earwicker am Anfang dieser Frage genannt hat.

Viel Glück !

1
Eric Ouellet

Ich würde vorschlagen, ToList aufzurufen. Ja, Sie führen die Aufzählung vorzeitig durch, haben jedoch weiterhin Zugriff auf Ihre Elementliste.

0
Jonathan Allen

Nein.

Sehen Sie diese Informationen irgendwo im Code, den Sie geschrieben haben?

Sie könnten argumentieren, dass der Compiler "sehen" kann, dass es nur zwei gibt, aber das würde bedeuten, dass er jede Iteratormethode analysieren müsste, um nur nach diesem spezifischen pathologischen Fall zu suchen. Und selbst wenn ja, wie würden Sie es angesichts der Grenzen eines IEnumerable lesen?

0
James Curran

Es liefert möglicherweise nicht die beste Leistung, aber Sie können LINQ verwenden, um die Elemente in einer IEnumerable zu zählen:

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