Считать элементы из IEnumerable <T>без итерации? - PullRequest
292 голосов
/ 04 октября 2008
private IEnumerable<string> Tables
{
    get
    {
        yield return "Foo";
        yield return "Bar";
    }
}

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

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

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

Ответы [ 19 ]

308 голосов
/ 04 октября 2008

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

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

197 голосов
/ 12 мая 2009

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

84 голосов
/ 04 октября 2008

Просто добавив немного информации:

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

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

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

11 голосов
/ 04 октября 2008

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

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

  1. Beyond Loops (предоставляет начальный класс EnumerableUtility, используемый в других сообщениях)
  2. Приложения Iterate (Начальная реализация)
  3. Сумасшедшие методы расширения: ToLazyList (оптимизация производительности)
10 голосов
/ 04 октября 2008

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

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

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

9 голосов
/ 12 мая 2009

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

Tables.ToList<string>().Count;
8 голосов
/ 04 октября 2008

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

8 голосов
/ 26 января 2012

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

5 голосов
/ 12 мая 2009

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

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

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

}

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

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

}
...