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

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

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

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

Ответы [ 19 ]

3 голосов
/ 25 апреля 2011

Это зависит от того, какая версия .Net и реализация вашего объекта IEnumerable. Microsoft исправила метод IEnumerable.Count для проверки реализации и использует ICollection.Count или ICollection .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
2 голосов
/ 12 августа 2009

Результат функции 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 голосов
/ 12 мая 2009

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

1 голос
/ 20 сентября 2011

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


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

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

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

Удачи!

0 голосов
/ 07 мая 2018

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

((IList<string>)Table).Count
0 голосов
/ 26 июня 2013

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

public int GetEnumerableCount(IEnumerable Enumerable)
{
    return (from object Item in Enumerable
            select Item).Count();
}
0 голосов
/ 18 января 2012

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

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

номер

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

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

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

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

...