Question

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

Disons que je veux les parcourir et écrire quelque chose comme traitement #n de #m.

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

J'espère avoir été clair.

Était-ce utile?

La solution

IEnumerable ne le supporte pas. C'est par conception. IEnumerable utilise l'évaluation paresseuse pour obtenir les éléments que vous demandez juste avant de les utiliser.

Si vous souhaitez connaître le nombre d'éléments sans les parcourir, vous pouvez utiliser ICollection < T > , une propriété Count .

Autres conseils

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;

Il tente donc de convertir ICollection < T > , qui possède une propriété Count , et l'utilise si possible. Sinon, il répète.

Par conséquent, votre meilleur choix est d'utiliser la méthode d'extension Count () sur votre objet IEnumerable < T > , car vous obtiendrez ainsi les meilleures performances possibles.

Ajoutez juste quelques informations supplémentaires:

L'extension Count () n'itéré pas toujours. Considérez Linq en Sql, où le compte va à la base de données, mais au lieu de ramener toutes les lignes, il lance la commande Sql Count () et renvoie le résultat à la place.

En outre, le compilateur (ou le moteur d’exécution) est suffisamment intelligent pour appeler la méthode Count () de l’objet, si elle en possède une. Donc, ce n’est pas comme le disent d’autres répondants, complètement ignorants et toujours en train d’itérer pour compter les éléments.

Dans de nombreux cas où le programmeur vérifie simplement si (enumerable.Count! = 0) à l'aide de la méthode d'extension Any () , comme dans si ( Enumerable.Any ()) est bien plus efficace avec l'évaluation paresseuse de linq car il peut court-circuiter une fois qu'il peut déterminer la présence d'éléments. C'est aussi plus lisible

Un de mes amis publie une série de billets de blog illustrant pourquoi vous ne pouvez pas le faire. Il crée une fonction qui renvoie un IEnumerable dans lequel chaque itération renvoie le nombre premier suivant, jusqu'à ulong.MaxValue , et l'élément suivant n'est pas calculé tant que vous ne le demandez pas. Question rapide et explicite: combien d'articles sont renvoyés?

Voici les messages, mais ils sont plutôt longs:

  1. Au-delà des boucles (fournit une classe initiale EnumerableUtility utilisée dans les autres articles)
  2. Applications d'Iterate (mise en oeuvre initiale)
  3. Méthodes d'extensions folles: ToLazyList (optimisations de performances)

IEnumerable ne peut pas compter sans itérer.

Sous " normal " Dans certaines circonstances, il serait possible pour les classes implémentant IEnumerable ou IEnumerable < T > ;, telles que List & T > ;, d'implémenter la méthode Count en renvoyant la propriété List < T > .Count. Toutefois, la méthode Count n'est pas en réalité une méthode définie sur la variable IEnumerable < T > ou interface IEnumerable. (Le seul qui soit, en fait, est GetEnumerator.) Et cela signifie qu’une implémentation spécifique à une classe ne peut pas être fournie.

Il s’agit plutôt d’une méthode d’extension, définie dans la classe statique Enumerable. Cela signifie qu'il peut être appelé sur n'importe quelle instance d'un fichier IEnumerable < T > classe dérivée, quelle que soit la mise en œuvre de cette classe. Mais cela signifie également qu'il est implémenté à un endroit unique, extérieur à l'une de ces classes. Ce qui bien sûr signifie qu'il doit être implémenté de manière totalement indépendante des internes de ces classes. Le seul moyen de compter est par itération.

Vous pouvez également effectuer les opérations suivantes:

Tables.ToList<string>().Count;

Non, pas en général. L'utilisation d'énumérables a notamment pour but de ne pas connaître l'ensemble des objets de l'énumération (à l'avance, voire pas du tout).

Vous pouvez utiliser System.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'.

Pour aller au-delà de votre question immédiate (à laquelle une réponse complète a été apportée par la négative), si vous souhaitez signaler les progrès lors du traitement d'un énumérable, vous pouvez consulter le message de mon blog Progression de la génération de rapports au cours des requêtes Linq .

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é cette méthode dans une méthode pour vérifier le contenu de IEnumerable contenu

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

}

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

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

.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

Voici une excellente discussion sur évaluation paresseuse et 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. Voici un exemple 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)

.

Le seul moyen d'avoir un décompte rapide consiste à utiliser un indexeur (comme un tableau) dans la collection d'origine. Pour créer du code générique avec un minimum d’exigences, vous pouvez utiliser IEnumerable, mais si vous avez également besoin du décompte, ma méthode préférée consiste à utiliser cette interface:


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

Si votre collection d'origine ne comporte aucun indexeur, votre implémentation de Count peut effectuer une itération sur la collection, avec le hit connu dans la performance O (n).

Si vous ne voulez pas utiliser quelque chose de similaire à IEnumAndCount, votre meilleur pari est de choisir Linq.Count pour les raisons données par Daniel Earwicker au début de cette question.

Bonne chance!

Non.

Voyez-vous ces informations disponibles 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 faudrait analyser chaque méthode d'itérateur en recherchant uniquement ce cas pathologique spécifique. Et même s’il le faisait, comment le liriez-vous, compte tenu des limites d’un IEnumerable?

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.

Les performances ne sont peut-être pas optimales, 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 < string > .ToArray < string > (). Length et tout fonctionne correctement.

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

((IList<string>)Table).Count
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top