Compter les éléments d'un IEnumerable sans itérer?

private IEnumerable<string> 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.

Existe-t-il un moyen de trouver la valeur de m sans itérer avant mon itération principale?

J'espère que je me suis fait clair.

270
demandé sur Erik Philips 2008-10-04 01:05:03

19 réponses

IEnumerable ne supporte pas cela. C'est par la conception. IEnumerable utilise une évaluation paresseuse pour obtenir les éléments que vous demandez juste avant d'en avoir besoin.

Si vous voulez connaître le nombre d'éléments sans itération sur eux, vous pouvez utiliser ICollection<T>, il a un Count la propriété.

279
répondu Mendelt 2017-03-03 03:02:20

La méthode d'extension System.Linq.Enumerable.Count sur IEnumerable<T> a l'implémentation suivante:

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;

Donc, il essaie de convertir en ICollection<T>, qui a une propriété Count, et l'utilise si possible. Sinon, il effectue une itération.

Votre meilleur pari est donc d'utiliser la méthode d'extension Count() sur votre objet IEnumerable<T>, car vous obtiendrez les meilleures performances possibles de cette façon.

175
répondu Daniel Earwicker 2016-03-02 01:20:57

Juste en ajoutant quelques informations supplémentaires:

L'extension Count() n'est pas toujours itérée. Considérez Linq à Sql, où le nombre va à la base de données, 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 runtime) est assez intelligent pour appeler la méthode objects Count() si elle en a une. Donc, c'est Pas comme le disent les autres intervenants, étant complètement ignorant et toujours itérant pour compter élément.

Dans de nombreux cas où le programmeur vérifie simplement if( enumerable.Count != 0 ) en utilisant la méthode d'extension Any(), comme dans 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 qu'il y a des éléments. C'est aussi plus lisible

77
répondu Robert Paulson 2016-09-29 17:18:31

IEnumerable ne peut pas compter sans itérer.

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

Plutôt, Count c'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 aussi qu'il est implémenté dans un seul endroit, externe à l'une de ces classes. Ce qui signifie Bien sûr qu'il doit être implémenté d'une manière complètement indépendante des internes de ces classes. La seule façon de faire le comptage est via l'itération.

10
répondu Chris Ammerman 2008-10-03 21:40:29

Un de mes amis a une série de messages de blog qui fournissent une illustration de pourquoi vous ne pouvez pas faire cela. 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 pas calculé jusqu'à ce que vous le demandiez. Rapide, pop question: combien d'articles sont retournés?

Voici les messages, mais ils sont un peu longs:

  1. Beyond Loops (fournit une classe EnumerableUtility initiale utilisée dans l'autre postes)
  2. Applications de Iterate (implémentation initiale)
  3. Méthodes D'extension folles: ToLazyList (optimisations de performances)
9
répondu Joel Coehoorn 2013-04-04 15:10:07

Non, pas en général. Un point en utilisant enumerables est que l'ensemble des objets dans l'énumération n'est pas connu à l'avance, ou même pas du tout).

8
répondu JesperE 2008-10-03 21:07:40

Vous pouvez utiliser le système.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());
    }
}

Vous obtiendrez le résultat '2'.

8
répondu prosseek 2012-01-26 19:02:13

Vous pouvez également faire ce qui suit:

Tables.ToList<string>().Count;
7
répondu Anatoliy Nikolaev 2014-06-24 05:50:43

Au-delà de votre question immédiate (qui a été complètement répondu par la négative), si vous cherchez à signaler des progrès tout en traitant un énumérable, vous pouvez regarder mon article de blog signalant des progrès pendant les requêtes Linq.

Il 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
      };
5
répondu Samuel Jack 2009-05-12 15:56:18

J'ai utilisé une telle méthode à l'intérieur d'une méthode pour vérifier le contenu IEnumberable

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

}

Dans une méthode comme celle-ci:

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

}
4
répondu Shahidul Haque 2013-09-29 07:46:52

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

Et ci-dessous est le MSIL de Ildasm pour le système.De base, dans lequel le Système.Linq réside.

.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
répondu prabug 2011-04-29 00:13:02

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

2
répondu JP Alioto 2009-05-12 15:41:50

Résultat du IEnumerable.La fonction Count() peut être fausse. 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(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());
    }
  }
}

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

2
répondu Roman Golubin 2009-08-12 16:41:30

La seule façon d'avoir un compte rapide est lorsque la collection d'origine a un indexeur (comme array). Afin de créer du code générique avec une exigence minimale, vous pouvez utiliser IEnumerable mais si vous avez besoin du compte aussi, ma façon préférée est d'utiliser cette interface:


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

Si votre collection d'origine n'a aucun indexeur, votre implémentation Count peut itérer sur la collection, avec le hit connu dans performance O (n).

Si vous ne voulez pas utiliser quelque chose de similaire à IEnumAndCount, votre meilleur pari est d'aller avec Linq.Comptez pour les raisons données par Daniel Earwicker près du haut de cette question.

Bonne chance !

1
répondu Eric Ouellet 2015-12-10 17:39:53

Non.

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

Vous pourriez faire valoir que le compilateur peut "voir" qu'il n'y en a que deux, mais cela signifierait qu'il devrait analyser chaque méthode d'itérateur à la recherche de ce cas pathologique spécifique. Et même si c'était le cas, comment le liseriez-vous, compte tenu des limites D'un IEnumerable?

0
répondu James Curran 2008-10-03 21:10:41

Je suggère D'appeler ToList. Oui, vous faites l'énumération tôt, mais vous avez toujours accès à votre liste d'éléments.

0
répondu Jonathan Allen 2008-10-04 05:00:20

Il 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();
}
0
répondu Hugo 2013-06-26 08:39:09

J'utilise IEnum<string>.ToArray<string>().Length et cela fonctionne bien.

-2
répondu Oliver Kötter 2012-01-18 09:58:00

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

((IList<string>)Table).Count
-2
répondu Me Hungry 2018-05-07 10:27:49