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

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

Скажем, я хочу повторить их и написать что-то вроде обработки #n из #m.

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

Надеюсь, я поняла.

Ответ 1

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

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

Ответ 2

Метод расширения 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>, так как вы получите максимальную производительность таким образом.

Ответ 3

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

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

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

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

Ответ 4

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

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

Ответ 5

IEnumerable не может рассчитывать без повторения.

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

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

Ответ 6

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

Ответ 7

Вы можете использовать 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".

Ответ 8

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

Tables.ToList<string>().Count;

Ответ 9

Выйдя за пределы вашего непосредственного вопроса (на который был дан ответ отрицательно), если вы хотите сообщить о прогрессе при обработке перечислимого, вам может потребоваться просмотреть мой пост в блоге Отчетность во время запросов 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
      };

Ответ 10

Я использовал такой способ внутри метода для проверки переданного в IEnumberable контенте

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

}

Внутри метода, подобного этому:

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

}

Ответ 11

Это зависит от какой версии .Net и реализации вашего объекта IEnumerable. Microsoft установила метод IEnumerable.Count для проверки реализации и использует ICollection.Count или ICollection <TSource> .Count, см. Подробности здесь https://connect.microsoft.com/VisualStudio/feedback/details/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

Ответ 13

Результат функции 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)

Ответ 14

Вы не можете сделать это без внутренней итерации. Это будет работать.

using System.Linq;
...    
Tables.Count();

Ответ 15

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


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

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

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

Удачи!

Ответ 16

Нет.

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

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

Ответ 17

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

Ответ 18

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

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

Ответ 19

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

Ответ 20

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

((IList<string>)Table).Count