2008-10-03 1 views
251
private IEnumerable<string> Tables 
{ 
    get 
    { 
     yield return "Foo"; 
     yield return "Bar"; 
    } 
} 

Предположим, я хочу повторить их и написать что-то вроде обработки #n из #m.Подсчитайте элементы из IEnumerable <T> без повтора?

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

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

ответ

254

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

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

+33

Я бы одобрил ICollection над IList, если вам не нужен доступ к списку индексатором. – 2008-10-03 21:17:20

+3

Я обычно просто беру List и IList по привычке. Но особенно если вы хотите реализовать их самостоятельно, ICollection проще, а также имеет свойство Count. Благодаря! – Mendelt 2008-10-03 22:05:20

+0

Так как я могу проверить счет, когда у меня есть данный IEnumerable? – Shimmy 2009-11-30 23:40:31

7

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

+0

Важным моментом, который вы подняли, является то, что даже когда вы получаете этот объект IEnumerable, вы должны увидеть, можете ли вы его отличить, чтобы выяснить, какой он тип. Это очень важный момент для тех, кто пытается использовать больше IEnumerable, как я, в моем коде. – PositiveGuy 2010-08-18 15:35:34

0

No.

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

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

9

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

Вот посты, но они вроде давно:

  1. Beyond Loops (обеспечивает начальный класс EnumerableUtility, используемый в других постах)
  2. Applications of Iterate (начальная реализация)
  3. Crazy Extention Methods: ToLazyList (Performance оптимизация)
9

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

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

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

69

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

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

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

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

0

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

7

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

Tables.ToList<string>().Count; 
2

Здесь большое обсуждение lazy evaluation и deferred execution. В основном вы должны материализовать список, чтобы получить это значение.

165

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

5

Выходя за пределы вашего непосредственного вопроса (на который был дан ответ на отрицательный ответ), если вы хотите сообщить о прогрессе при обработке перечислимого, вы можете посмотреть мое сообщение в блоге Reporting Progress During Linq Queries.

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

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

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

2

Это зависит от того, какой версии .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 
1

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


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

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

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

Удачи вам!

-2

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

8

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

3

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

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

} 

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

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

} 
0

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

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

Вы не можете сделать это без inte по крайней мере, итерации. Это сработает.

using System.Linq; 
...  
Tables.Count(); 
Смежные вопросы