Подсчитать элементы из IEnumerable<T> без итерации?

StackOverflow https://stackoverflow.com/questions/168901

  •  05-07-2019
  •  | 
  •  

Вопрос

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

Допустим, я хочу выполнить итерацию по ним и написать что-то вроде обработки #n из #m.

Есть ли способ узнать значение m без повторения перед основной итерацией?

Надеюсь, я ясно выразился.

Это было полезно?

Решение

IEnumerable не поддерживает это. Это по замыслу. IEnumerable использует ленивую оценку, чтобы получить элементы, которые вы запрашиваете, перед тем, как они понадобятся.

Если вы хотите узнать количество элементов без итерации по ним, вы можете использовать ICollection < T > , у него есть свойство Count .

Другие советы

Метод расширения System.Linq.Enumerable.Count в IEnumerable < T > имеет следующую реализацию:

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;

Поэтому он пытается привести к ICollection < T > , который имеет свойство Count , и использует его, если это возможно. В противном случае это повторяется.

Поэтому лучше всего использовать метод расширения Count () для вашего объекта IEnumerable < T > , так как вы получите максимально возможную производительность.

Просто добавьте дополнительную информацию:

Расширение Count () не всегда повторяется. Рассмотрим Linq to Sql, где счетчик отправляется в базу данных, но вместо возвращения всех строк он запускает команду Sql Count () и возвращает этот результат.

Кроме того, компилятор (или среда выполнения) достаточно умен, чтобы вызывать объектный метод Count () , если он у него есть. Так что не , как говорят другие респонденты, совершенно неосведомлен и всегда выполняет итерацию для подсчета элементов.

Во многих случаях, когда программист просто проверяет if (enumerable.Count! = 0) , используя метод расширения Any () , как в if ( enumerable.Any ()) гораздо более эффективен с ленивой оценкой linq, поскольку он может закорачивать, как только он может определить, есть ли какие-либо элементы. Это также более читабельно

У моего друга есть серия постов в блоге, которые иллюстрируют, почему вы не можете этого сделать. Он создает функцию, которая возвращает IEnumerable, где каждая итерация возвращает следующее простое число, вплоть до ulong.MaxValue , и следующий элемент не рассчитывается до тех пор, пока вы его не попросите. Быстрый, всплывающий вопрос: сколько предметов возвращено?

Вот сообщения, но они довольно длинные:

<Ол>
  • Beyond Loops (предоставляет начальный класс EnumerableUtility, используемый в других публикациях)
  • Приложения итерации (начальная реализация)
  • Сумасшедшие методы расширения: ToLazyList (оптимизация производительности)
  • IEnumerable не может рассчитывать без итерации.

    Под " нормальным " В таких случаях было бы возможно, чтобы классы, реализующие IEnumerable или IEnumerable < T > ;, например List < T >, реализовали метод Count, возвращая свойство List < T > .Count. Однако метод Count на самом деле не является методом, определенным в IEnumerable < T > или IEnumerable интерфейс. (Фактически единственным является GetEnumerator.) И это означает, что для него не может быть обеспечена реализация для конкретного класса.

    Скорее, Count это метод расширения, определенный для статического класса Enumerable. Это означает, что он может быть вызван для любого экземпляра IEnumerable < T > производный класс, независимо от реализации этого класса. Но это также означает, что он реализован в одном месте, вне любого из этих классов. Что, конечно, означает, что он должен быть реализован способом, который полностью независим от внутренних функций этих классов. Единственный способ подсчета - итерация.

    В качестве альтернативы вы можете сделать следующее:

    Tables.ToList<string>().Count;
    

    Нет, не в общем. Одним из аспектов использования перечислимых элементов является то, что фактический набор объектов в перечислении неизвестен (заранее или даже вообще).

    Вы можете использовать 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());
        }
    }
    

    Вы получите результат «2».

    Если выйти за рамки вашего непосредственного вопроса (на который был получен исчерпывающий ответ), если вы хотите сообщить о прогрессе при обработке перечислимого, вы можете посмотреть мое сообщение в блоге Отчет о ходе выполнения запросов Linq .

    Это позволяет вам сделать это:

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

    Я использовал такой способ внутри метода, чтобы проверить переданный в IEnumerable контент

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

    Внутри такого метода:

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

    Это зависит от того, какая версия .Net и реализация вашего объекта IEnumerable. Microsoft исправила метод IEnumerable.Count для проверки реализации и использует ICollection.Count или ICollection < TSource > .Count, подробности см. Здесь https://connect.microsoft.com/VisualStudio/ обратная связь / подробности / 454130

    А ниже приведен MSIL от Ildasm для System.Core, в котором находится 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
    

    Вот отличная дискуссия о ленивая оценка и отложенное выполнение . По сути, вы должны материализовать список, чтобы получить это значение.

    Результат функции IEnumerable.Count () может быть неправильным. Это очень простой пример для тестирования:

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

    Результат должен быть (7,7,3,3), но фактический результат - (7,7,3,17)

    Единственный способ быстрого подсчета - это когда в исходной коллекции есть индексатор (например, массив). Чтобы создать универсальный код с минимальными требованиями, вы можете использовать IEnumerable, но если вам нужен счетчик, то я предпочитаю использовать этот интерфейс:

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

    Если ваша исходная коллекция не имеет никакого индексатора, ваша реализация Count может перебирать коллекцию с известным падением производительности O (n).

    Если вы не хотите использовать что-то похожее на IEnumAndCount, лучше всего выбрать Linq.Count по причинам, указанным Дэниелом Эрвикером в верхней части этого вопроса.

    Удачи!

    Нет.

    Видите ли вы эту информацию где-нибудь в написанном вами коде?

    Вы можете возразить, что компилятор может «видеть», что их только два, но это будет означать, что ему придется анализировать каждый метод итератора, выискивая только этот конкретный патологический случай.И даже если бы это было так, как бы вы это прочитали, учитывая ограничения IEnumerable?

    Я бы предложил вызвать ToList. Да, вы выполняете перечисление рано, но у вас все еще есть доступ к вашему списку элементов.

    Это может не дать наилучшей производительности, но вы можете использовать LINQ для подсчета элементов в IEnumerable:

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

    Я использую IEnum < string > .ToArray < string > (). Length , и он отлично работает.

    Я использую такой код, если у меня есть список строк:

    ((IList<string>)Table).Count
    
    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top