Подсчитайте элементы из 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
Ответ 12
Вот отличная дискуссия о ленивая оценка и отложенное исполнение. В основном вы должны материализовать список, чтобы получить это значение.
Ответ 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