Zählen Sie die Elemente aus einem IEnumerable ohne Iterieren?
-
05-07-2019 - |
Frage
private IEnumerable<string> Tables
{
get
{
yield return "Foo";
yield return "Bar";
}
}
Lassen Sie uns sagen, dass ich auf diejenigen iterieren wollen und schreiben etwas wie #n von #m Verarbeitung.
Gibt es eine Möglichkeit ich den Wert von m ohne Iterieren vor meiner Haupt Iteration herausfinden kann?
Ich hoffe, dass ich mich klar.
Lösung
IEnumerable
dies nicht unterstützt. Das ist von Entwurf. IEnumerable
verwendet lazy evaluation die Elemente, die Sie für nur fragen zu erhalten, bevor Sie sie benötigen.
Wenn Sie die Anzahl der Elemente wissen wollen, ohne über sie iterieren Sie ICollection<T>
verwenden können, hat es eine Count
Eigenschaft.
Andere Tipps
Die System.Linq.Enumerable.Count
Erweiterungsmethode auf IEnumerable<T>
hat die folgende Umsetzung:
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;
So ist es versucht, ICollection<T>
zu werfen, die eine Count
Eigenschaft hat, und verwendet diese, wenn möglich. Andernfalls durchläuft es.
So Ihre beste Wette ist die Count()
Erweiterungsmethode auf Ihrem IEnumerable<T>
Objekt zu verwenden, wie Sie die beste Leistung erhalten werden möglich, dass Art und Weise.
Hinzufügen nur zusätzliche einige Informationen:
Die Count()
Erweiterung nicht immer wiederholen. Betrachten Sie Linq to Sql, wo die Zählung in die Datenbank geht, sondern bringen alle Zeilen zurück, es gibt die Sql Count()
Befehl und kehrt die stattdessen zur Folge haben.
Darüber hinaus ist der Compiler (oder Laufzeit) intelligent genug, dass es die Objekte Count()
Methode aufrufen, wenn sie eine hat. So ist es nicht als andere Responder sagen, völlig unwissend und Iterieren immer um Elemente zu zählen.
In vielen Fällen, in denen der Programmierer nur if( enumerable.Count != 0 )
Überprüfung wird die Any()
Extension-Methode verwendet wird, wie in if( enumerable.Any() )
wesentlich effizienter mit Linq fauler Auswertung ist, wie es Kurzschluss kann, sobald es irgendwelche Elemente bestimmen kann. Es ist auch besser lesbar
Ein Freund von mir hat eine Reihe von Blog-Posts, die für eine Darstellung geben, warum Sie dies nicht tun können. Er schafft Funktion, die eine IEnumerable, wo jede Iteration gibt die nächste Primzahl zurückzukehren, den ganzen Weg ulong.MaxValue
, und der nächste Punkt wird nicht berechnet, bis Sie danach fragen. Schnell, Pop Frage: wie viele Elemente zurückgegeben werden
Hier sind die Beiträge, aber sie sind irgendwie lang:
- Jenseits Loops (liefert eine anfängliche EnumerableUtility Klasse in den anderen Beiträgen verwendet)
- Anwendungen von Iterate (Initial-Implementierung)
- verrückt Extention Methoden: ToLazyList (Performance-Optimierungen)
IEnumerable kann nicht ohne Iterieren zählen.
Unter "normalen" Umständen wäre es möglich, für die Klassen der Umsetzung IEnumerable oder IEnumerable
Vielmehr Count ist es eine Erweiterungsmethode, auf der statischen Klasse definiert Enumerable. Das bedeutet, kann es auf jeden Fall eines IEnumerable
Alternativ können Sie wie folgt vor:
Tables.ToList<string>().Count;
Nein, nicht im Allgemeinen. Ein Punkt enumerables in Verwendung ist, dass die tatsächliche Menge von Objekten in der Aufzählung ist nicht bekannt (im Voraus oder sogar überhaupt).
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 werden das Ergebnis erhalten '2'.
über Ihre unmittelbare Frage gehen (was im negativen gründlich beantwortet wurde), wenn Sie schauen, Fortschritte zu berichten, während der Verarbeitung einen enumerable, könnten Sie in meiner Blog-Post aussehen wollen? Berichterstattung Fortschritt Während Linq Abfragen .
Damit können Sie dies 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 eine solche Art und Weise in einem Verfahren der in IEnumberable
Inhalt übergeben zu überprüfen
if( iEnum.Cast<Object>().Count() > 0)
{
}
In einem Verfahren wie folgt aus:
GetDataTable(IEnumberable iEnum)
{
if (iEnum != null && iEnum.Cast<Object>().Count() > 0) //--- proceed further
}
Es hängt davon ab, welche Version von .Net und Umsetzung Ihrer IEnumerable-Objekt.
Microsoft die IEnumerable.Count Methode für die Umsetzung zu überprüfen, festgelegt hat, und verwendet die ICollection.Count oder ICollection
Und unten ist die MSIL von Ildasm für System.Core, in dem die 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
Hier ist eine große Diskussion über lazy evaluation und verzögerte Ausführung . Grundsätzlich müssen Sie die Liste materialisieren, um diesen Wert zu erhalten.
Ergebnis der IEnumerable.Count () Funktion kann falsch sein. Dies ist ein sehr einfaches Beispiel zu 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());
}
}
}
Ergebnis muss (7,7,3,3), aber tatsächliches Ergebnis ist (7,7,3,17)
Der einzige Weg, um eine schnelle Zählung zu haben ist, wenn die ursprüngliche Sammlung einen Indexer (wie Array) hat. Um generischen Code mit einer Mindestanforderung erstellen können Sie IEnumerable verwenden, aber wenn Sie die Zählung müssen auch dann ist mein bevorzugter Weg, um diese Schnittstelle zu verwenden:
public interface IEnumAndCount<out T> : IEnumerable<T>
{
int Count { get; }
}
Wenn Sie Ihre ursprüngliche Sammlung keine Indexer hat, Ihre Count Implementierung über die Auflistung durchlaufen kann, mit dem bekannten Hit in der Leistung O (n).
Wenn Sie nicht etwas ähnliches wie IEnumAndCount verwenden wollen, ist Ihre beste Wette mit Linq.Count aus Gründen von Daniel Earwicker in der Nähe der Spitze dieser Frage gegeben gehen.
Viel Glück!
Nein.
Sehen Sie diese Informationen zur Verfügung überall im Code, den Sie geschrieben haben?
Sie könnten argumentieren, dass der Compiler „sehen“ kann, dass es nur zwei ist, aber das würde bedeuten, dass es zu betrachten jedes Iteratormethode analysieren müßte nur für diesen spezifischen pathologischen Fall. Und selbst wenn es so wäre, wie würden Sie es lesen, die Grenzen eines IEnumerable gegeben?
Ich würde vorschlagen, ToList anrufen. Ja, Sie tun, um die Auszählung zu früh, aber Sie noch Zugriff auf Ihre Liste von Elementen haben.
Es kann die beste Leistung nicht liefern, aber Sie können LINQ verwenden Sie die Elemente in einem IEnumerable zu zählen:
public int GetEnumerableCount(IEnumerable Enumerable)
{
return (from object Item in Enumerable
select Item).Count();
}
Ich benutze IEnum<string>.ToArray<string>().Length
und es funktioniert gut.
ich einen solchen Code zu verwenden, wenn ich Liste von Strings haben:
((IList<string>)Table).Count