Respostas:
IEnumerable
não suporta isso. Isso é por design. IEnumerable
usa avaliação lenta para obter os elementos solicitados antes de precisar deles.
Se você deseja saber o número de itens sem iterar sobre eles, pode usar ICollection<T>
, ele possui uma Count
propriedade
O System.Linq.Enumerable.Count
método de extensão on IEnumerable<T>
tem a seguinte implementação:
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;
Por isso, tenta converter para ICollection<T>
, que possui uma Count
propriedade, e a usa, se possível. Caso contrário, ele itera.
Portanto, sua melhor aposta é usar o Count()
método de extensão em seu IEnumerable<T>
objeto, pois assim você obterá o melhor desempenho possível.
ICollection<T>
primeiro.
T
em IEnumerator<T> enumerator = source.GetEnumerator()
, simplesmente você pode fazer isso: IEnumerator enumerator = source.GetEnumerator()
e deve funcionar!
IEnumerable<T>
herda, o IDisposable
que permite que a using
declaração a descarte automaticamente. IEnumerable
não. Então, se você chamar GetEnumerator
qualquer forma, você deve terminar comvar d = e as IDisposable; if (d != null) d.Dispose();
Apenas adicionando algumas informações extras:
A Count()
extensão nem sempre itera. Considere Linq to Sql, onde a contagem vai para o banco de dados, mas em vez de retornar todas as linhas, ele emite o Count()
comando Sql e retorna esse resultado.
Além disso, o compilador (ou tempo de execução) é inteligente o suficiente para chamar o Count()
método de objetos se tiver um. Portanto, não é como os outros respondentes dizem, sendo completamente ignorante e sempre iterando para contar elementos.
Em muitos casos em que o programador está apenas checando if( enumerable.Count != 0 )
usando o Any()
método de extensão, if( enumerable.Any() )
é muito mais eficiente com a avaliação preguiçosa do linq, pois pode causar um curto-circuito quando determinar que existem elementos. Também é mais legível
.Count
propriedade, pois ela sempre sabe o tamanho. Ao consultar collection.Count
não há computação adicional, ele simplesmente retorna a contagem já conhecida. Mesmo Array.length
até onde eu sei. No entanto, .Any()
obtém o enumerador da fonte usando using (IEnumerator<TSource> enumerator = source.GetEnumerator())
e retorna true, se puder enumerator.MoveNext()
. Para coleções if(collection.Count > 0)
:, matrizes: if(array.length > 0)
e em enumeráveis, faça if(collection.Any())
.
IQueryably<T>
em a, IEnumerable<T>
ele não emitirá uma contagem de sql. Quando escrevi isso, o Linq to Sql era novo; Eu acho que estava pressionando para usar, Any()
porque para enumeráveis, coleções e sql era muito mais eficiente e legível (em geral). Obrigado por melhorar a resposta.
Um amigo meu tem uma série de postagens no blog que fornecem uma ilustração do motivo pelo qual você não pode fazer isso. Ele cria uma função que retorna um IEnumerable em que cada iteração retorna o próximo número primo, até o fim ulong.MaxValue
, e o próximo item não é calculado até que você o solicite. Pergunta rápida e pop: quantos itens são devolvidos?
Aqui estão as postagens, mas são longas:
EnumerableFeatures
objeto) não exigiria que os enumeradores fizessem algo difícil, mas seria capaz de fazer essas perguntas (junto com outros como "Você pode prometer sempre retorne a mesma sequência de itens "," Você pode ser exposto com segurança a códigos que não devem alterar sua coleção subjacente "etc.) seria muito útil.
set yield options
declaração ou algo semelhante para apoiá-lo. Se projetado corretamente, um IEnhancedEnumerator
poderia fazer coisas como LINQ muito mais útil, eliminando um monte de "defensiva" ToArray
ou ToList
chamadas, especialmente ...
Enumerable.Concat
são usadas para combinar uma coleção grande que sabe muito sobre si mesma com uma coleção pequena que não sabe.
IEnumerable não pode contar sem iterar.
Em circunstâncias "normais", seria possível para as classes que implementam IEnumerable ou IEnumerable <T>, como List <T>, implementar o método Count retornando a propriedade List <T> .Count. No entanto, o método Count não é realmente um método definido na interface IEnumerable <T> ou IEnumerable. (O único que é, de fato, é GetEnumerator.) E isso significa que não é possível fornecer uma implementação específica de classe.
Em vez disso, Count é um método de extensão, definido na classe estática Enumerable. Isso significa que pode ser chamado em qualquer instância de uma classe derivada IEnumerable <T>, independentemente da implementação dessa classe. Mas também significa que é implementado em um único local, externo a qualquer uma dessas classes. O que, é claro, significa que deve ser implementado de maneira completamente independente dos internos dessas classes. A única maneira de fazer a contagem é através da iteração.
Como alternativa, você pode fazer o seguinte:
Tables.ToList<string>().Count;
Não, não em geral. Um ponto no uso de enumeráveis é que o conjunto real de objetos na enumeração não é conhecido (antecipadamente ou mesmo).
Você pode usar o 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());
}
}
Você obterá o resultado '2'.
Indo além da sua pergunta imediata (que foi totalmente respondida em negativo), se você estiver procurando relatar o progresso enquanto processa um enumerável, consulte a minha postagem no blog Relatando o progresso durante as consultas Linq .
Permite fazer isso:
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
};
Eu usei dessa maneira dentro de um método para verificar o IEnumberable
conteúdo passado
if( iEnum.Cast<Object>().Count() > 0)
{
}
Dentro de um método como este:
GetDataTable(IEnumberable iEnum)
{
if (iEnum != null && iEnum.Cast<Object>().Count() > 0) //--- proceed further
}
bool hasContents = false; if (iEnum != null) foreach (object ob in iEnum) { hasContents = true; ... your code per ob ... }
.
Depende da versão do .Net e da implementação do seu objeto IEnumerable. A Microsoft corrigiu o método IEnumerable.Count para verificar a implementação e usa o ICollection.Count ou ICollection <TSource> .Count, consulte os detalhes aqui https://connect.microsoft.com/VisualStudio/feedback/details/454130
E abaixo está o MSIL da Ildasm para System.Core, no qual o System.Linq reside.
.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
Aqui está uma ótima discussão sobre avaliação lenta e execução adiada . Basicamente, você precisa materializar a lista para obter esse valor.
O resultado da função IEnumerable.Count () pode estar errado. Esta é uma amostra muito simples para testar:
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());
}
}
}
O resultado deve ser (7,7,3,3), mas o resultado real é (7,7,3,17)
A melhor maneira que encontrei é contar convertendo-o em uma lista.
IEnumerable<T> enumList = ReturnFromSomeFunction();
int count = new List<T>(enumList).Count;
Eu sugeriria chamar a ToList. Sim, você está fazendo a enumeração antecipada, mas ainda tem acesso à sua lista de itens.
Pode não produzir o melhor desempenho, mas você pode usar o LINQ para contar os elementos em um IEnumerable:
public int GetEnumerableCount(IEnumerable Enumerable)
{
return (from object Item in Enumerable
select Item).Count();
}
Eu acho que a maneira mais fácil de fazer isso
Enumerable.Count<TSource>(IEnumerable<TSource> source)
Referência: system.linq.enumerable
Eu uso IEnum<string>.ToArray<string>().Length
e funciona bem.
IEnum<string>.Count();
"?
Eu uso esse código, se eu tiver uma lista de strings:
((IList<string>)Table).Count