Compter les éléments d’un IEnumerable sans itération?

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

Disons que je veux itérer sur ceux-ci et écrire quelque chose comme le traitement #n de #m.

Est-il possible de trouver la valeur de m sans itérer avant mon itération principale?

J’espère que je me suis fait comprendre.

IEnumerable ne supporte pas cela. C’est par conception. IEnumerable utilise l’évaluation paresseuse pour obtenir les éléments que vous demandez juste avant que vous en ayez besoin.

Si vous voulez connaître le nombre d’éléments sans les itérer, vous pouvez utiliser ICollection , il possède une propriété Count .

La méthode d’extension System.Linq.Enumerable.Count sur IEnumerable a l’implémentation suivante:

 ICollection c = source as ICollection; if (c != null) return c.Count; int result = 0; using (IEnumerator enumerator = source.GetEnumerator()) { while (enumerator.MoveNext()) result++; } return result; 

Il essaie donc de ICollection vers ICollection , qui possède une propriété Count , et l’utilise si possible. Sinon, il itère.

Donc, le mieux est d’utiliser la méthode d’extension Count() sur votre object IEnumerable , car vous obtiendrez ainsi les meilleures performances possibles.

Il suffit d’append des informations supplémentaires:

L’extension Count() n’itère pas toujours. Considérez Linq to Sql, où le compte va à la firebase database, mais au lieu de ramener toutes les lignes, il émet la commande Sql Count() et renvoie ce résultat à la place.

De plus, le compilateur (ou le runtime) est suffisamment intelligent pour appeler la méthode des objects Count() s’il en a une. Ce n’est donc pas comme les autres intervenants le disent, étant complètement ignorant et toujours itérant pour pouvoir compter des éléments.

Dans de nombreux cas où le programmeur vérifie simplement if( enumerable.Count != 0 ) utilisant la méthode d’extension Any() , comme if( enumerable.Any() ) est beaucoup plus efficace avec l’évaluation paresseuse de linq car il peut court-circuiter une fois qu’il peut déterminer, il y a des éléments. C’est aussi plus lisible

IEnumerable ne peut pas compter sans itération.

Dans des circonstances “normales”, il serait possible que les classes implémentant IEnumerable ou IEnumerable , telles que List , implémentent la méthode Count en renvoyant la propriété List .Count. Cependant, la méthode Count n’est en fait pas une méthode définie sur l’interface IEnumerable ou IEnumerable. (Le seul qui soit, en fait, est GetEnumerator.) Et cela signifie qu’une implémentation spécifique à une classe ne peut pas lui être fournie.

Au contraire, Count est une méthode d’extension, définie sur la classe statique Enumerable. Cela signifie qu’il peut être appelé sur n’importe quelle instance d’une classe dérivée IEnumerable , quelle que soit l’implémentation de cette classe. Mais cela signifie également qu’il est implémenté dans un lieu unique, externe à l’une de ces classes. Ce qui bien sûr signifie qu’il doit être implémenté de manière totalement indépendante de ces internes de classe. La seule façon de compter est via l’itération.

Un de mes amis a une série d’articles de blog qui illustrent pourquoi vous ne pouvez pas le faire. Il crée une fonction qui renvoie un IEnumerable où chaque itération renvoie le nombre premier suivant, jusqu’à ulong.MaxValue , et l’élément suivant n’est calculé que lorsque vous le demandez. Question rapide et pop: combien d’articles sont retournés?

Voici les articles, mais ils sont assez longs:

  1. Beyond Loops (fournit une classe initiale EnumerableUtility utilisée dans les autres articles)
  2. Applications d’itération (implémentation initiale)
  3. Méthodes d’extension fou: ToLazyList (optimisations de performances)

Non, pas en général Un point dans l’utilisation des énumérables est que l’ensemble d’objects dans l’énumération n’est pas connu (à l’avance ou même pas du tout).

Vous pouvez utiliser System.Linq.

 using System; using System.Collections.Generic; using System.Linq; public class Test { private IEnumerable Tables { get { yield return "Foo"; yield return "Bar"; } } static void Main() { var x = new Test(); Console.WriteLine(x.Tables.Count()); } } 

Vous aurez le résultat ‘2’.

Sinon, vous pouvez faire ce qui suit:

 Tables.ToList().Count; 

Au-delà de votre question immédiate (qui a reçu une réponse négative par la négative), si vous souhaitez signaler des progrès tout en traitant un énumérable, vous pouvez consulter mon article sur le blog.

Cela vous permet de faire ceci:

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

J’ai utilisé une telle méthode dans une méthode pour vérifier le contenu transmis dans IEnumberable

 if( iEnum.Cast().Count() > 0) { } 

Dans une méthode comme celle-ci:

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

Cela dépend de la version de .Net et de l’implémentation de votre object IEnumerable. Microsoft a corrigé la méthode IEnumerable.Count pour vérifier l’implémentation et utilise ICollection.Count ou ICollection .Count, voir les détails ici https://connect.microsoft.com/VisualStudio/feedback/details/454130

Et ci-dessous est le MSIL de Ildasm pour System.Core, dans lequel réside le System.Linq.

 .method public hidebysig static int32 Count(class  [mscorlib]System.Collections.Generic.IEnumerable`1 source) cil managed { .custom instance void System.Runtime.ComstackrServices.ExtensionAtsortingbute::.ctor() = ( 01 00 00 00 ) // Code size 85 (0x55) .maxstack 2 .locals init (class [mscorlib]System.Collections.Generic.ICollection`1 V_0, class [mscorlib]System.Collections.ICollection V_1, int32 V_2, class [mscorlib]System.Collections.Generic.IEnumerator`1 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(ssortingng) IL_000d: throw IL_000e: ldarg.0 IL_000f: isinst class [mscorlib]System.Collections.Generic.ICollection`1 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::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 class [mscorlib]System.Collections.Generic.IEnumerable`1::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 

Voici une excellente discussion sur l’ évaluation paresseuse et l’ exécution différée . Fondamentalement, vous devez matérialiser la liste pour obtenir cette valeur.

Le résultat de la fonction IEnumerable.Count () peut être incorrect. Ceci est un échantillon très simple à tester:

 using System; using System.Collections.Generic; using System.Linq; using System.Collections; namespace Test { class Program { static void Main(ssortingng[] 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 chunk in result) { cnt = chunk.Count(); Console.WriteLine(cnt); } cnt = result.Count(); Console.WriteLine(cnt); Console.ReadLine(); } } static class LinqExt { public static IEnumerable> Split(this IEnumerable source, int chunkLength) { if (chunkLength <= 0) throw new ArgumentOutOfRangeException("chunkLength", "chunkLength must be greater than 0"); IEnumerable result = null; using (IEnumerator enumerator = source.GetEnumerator()) { while (enumerator.MoveNext()) { result = GetChunk(enumerator, chunkLength); yield return result; } } } static IEnumerable GetChunk(IEnumerator source, int chunkLength) { int x = chunkLength; do yield return source.Current; while (--x > 0 && source.MoveNext()); } } } 

Le résultat doit être (7,7,3,3) mais le résultat réel est (7,7,3,17)

La seule façon d’avoir un compte rapide est lorsque la collection d’origine a un indexeur (comme le tableau). Pour créer un code générique avec une exigence minimale, vous pouvez utiliser IEnumerable, mais si vous avez également besoin du compte, ma préférence est d’utiliser cette interface:

 public interface IEnumAndCount : IEnumerable { int Count { get; } } 

Si votre collection d’origine n’a pas d’indexeur, votre implémentation Count peut effectuer une itération sur la collection, avec le hit connu dans les performances O (n).

Si vous ne voulez pas utiliser quelque chose de similaire à IEnumAndCount, le mieux est de choisir Linq.Count pour les raisons données par Daniel Earwicker en haut de cette question.

Bonne chance !

Non.

Voyez-vous cette information disponible n’importe où dans le code que vous avez écrit?

Vous pourriez argumenter que le compilateur peut “voir” qu’il n’y en a que deux, mais cela signifierait qu’il devrait parsingr chaque méthode d’iterator en ne cherchant que ce cas pathologique spécifique. Et même si c’était le cas, comment le liriez-vous, compte tenu des limites d’un IEnumerable?

Je suggère d’appeler ToList. Oui, vous faites l’énumération plus tôt, mais vous avez toujours access à votre liste d’articles.

Cela peut ne pas donner les meilleures performances, mais vous pouvez utiliser LINQ pour compter les éléments dans un IEnumerable:

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

J’utilise IEnum.ToArray().Length et ça marche bien.

J’utilise un tel code, si j’ai une liste de chaînes:

 ((IList)Table).Count