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.
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.
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.
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
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:
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.
Alternativ können Sie Folgendes tun:
Tables.ToList<string>().Count;
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).
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'.
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
};
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
}
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
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).
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.
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 !
Ich würde vorschlagen, ToList aufzurufen. Ja, Sie führen die Aufzählung vorzeitig durch, haben jedoch weiterhin Zugriff auf Ihre Elementliste.
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?
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();
}