Contar os itens de um IEnumerable sem iterar?

private IEnumerable Tables { get { yield return "Foo"; yield return "Bar"; } } 

Digamos que eu queira iterar nelas e escrever algo como processar #n de #m.

Existe uma maneira de descobrir o valor de m sem iterar antes de minha iteração principal?

Espero ter me esclarecido.

IEnumerable não suporta isso. Isso é por design. IEnumerable usa avaliação lenta para obter os elementos solicitados antes de você precisar deles.

Se você quiser saber o número de itens sem iterar sobre eles, você pode usar ICollection , ele tem uma propriedade Count .

O método de extensão System.Linq.Enumerable.Count no IEnumerable tem a seguinte implementação:

 ICollection c = source as ICollection; if (c != null) return c.Count; int result = 0; using (IEnumerator enumerator = source.GetEnumerator()) { while (enumerator.MoveNext()) result++; } return result; 

Por isso, tenta converter para ICollection , que tem uma propriedade Count , e usa isso se possível. Caso contrário, itera.

Portanto, sua melhor opção é usar o método de extensão Count() em seu object IEnumerable , pois você obterá o melhor desempenho possível dessa maneira.

Apenas adicionando algumas informações extras:

A extensão Count() nem sempre itera. Considere Linq para Sql, em que a contagem vai para o database, mas, em vez de retornar todas as linhas, emite o comando Sql Count() e retorna esse resultado.

Além disso, o compilador (ou tempo de execução) é inteligente o suficiente para chamar o método Count() se tiver um. Portanto, não é como os outros respondentes dizem, sendo completamente ignorantes e sempre interagindo para contar elementos.

Em muitos casos, onde o programador está apenas verificando if( enumerable.Count != 0 ) usando o método de extensão Any() , como em if( enumerable.Any() ) é muito mais eficiente com a avaliação preguiçosa do linq, pois ele pode causar curto-circuito uma vez pode determinar que existem elementos. Também é mais legível

IEnumerable não pode contar sem iterar.

Em circunstâncias “normais”, seria possível para classs implementando IEnumerable ou IEnumerable , como List , para implementar o método Count, retornando a propriedade List .Count. No entanto, o método Count não é realmente um método definido na interface IEnumerable ou IEnumerable. (O único que é, na verdade, é GetEnumerator.) E isso significa que uma implementação específica da class não pode ser fornecida para ele.

Em vez disso, Count é um método de extensão, definido na class estática Enumerable. Isso significa que ele pode ser chamado em qualquer instância de uma class derivada IEnumerable , independentemente da implementação dessa class. Mas isso também significa que ele é implementado em um único local, externo a qualquer uma dessas classs. O que, claro, significa que deve ser implementado de uma maneira completamente independente dos componentes internos dessas classs. A única maneira de fazer isso é através da iteração.

Um amigo meu tem uma série de posts que fornecem uma ilustração de por que você não pode fazer isso. Ele cria uma function que retorna um IEnumerable, onde cada iteração retorna o próximo número primo, até o ulong.MaxValue , e o próximo item não é calculado até que você o solicite. Pergunta rápida, pop: quantos itens são retornados?

Aqui estão os posts, mas eles são longos:

  1. Beyond Loops (fornece uma class inicial EnumerableUtility usada nos outros posts)
  2. Aplicações de iteração (implementação inicial)
  3. Métodos de Extensões malucas: ToLazyList (otimizações de desempenho)

Não, não em geral. Um ponto no uso de enumeráveis ​​é que o conjunto real de objects na enumeração não é conhecido (com antecedência ou mesmo de forma alguma).

Você pode usar o System.Linq.

 using System; using System.Collections.Generic; using System.Linq; public class Test { private IEnumerable 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’.

Alternativamente, você pode fazer o seguinte:

 Tables.ToList().Count; 

Indo além de sua pergunta imediata (que foi completamente respondida pelo negativo), se você estiver querendo relatar o progresso enquanto processa um enumerável, você pode querer olhar para o meu post Progresso de Relatórios Durante Consultas Linq .

Isso permite que você faça 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 esse caminho dentro de um método para verificar o conteúdo IEnumberable passado

 if( iEnum.Cast().Count() > 0) { } 

Dentro de um método como este:

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

Depende de qual versão do .Net e da implementação do seu object IEnumerable. A Microsoft corrigiu o método IEnumerable.Count para verificar a implementação e usa o ICollection.Count ou ICollection .Count, consulte os detalhes aqui https://connect.microsoft.com/VisualStudio/feedback/details/454130

E abaixo está o MSIL do Ildasm para System.Core, no qual o System.Linq reside.

 .method public hidebysig static int32 Count(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 preguiçosa e execução adiada . Basicamente você tem que materializar a lista para obter esse valor.

Resultado da function 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 chunk in result) { cnt = chunk.Count(); Console.WriteLine(cnt); } cnt = result.Count(); Console.WriteLine(cnt); Console.ReadLine(); } } static class LinqExt { public static IEnumerable> Split(this IEnumerable source, int chunkLength) { if (chunkLength < = 0) throw new ArgumentOutOfRangeException("chunkLength", "chunkLength must be greater than 0"); IEnumerable result = null; using (IEnumerator enumerator = source.GetEnumerator()) { while (enumerator.MoveNext()) { result = GetChunk(enumerator, chunkLength); yield return result; } } } static IEnumerable GetChunk(IEnumerator 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 única maneira de ter uma contagem rápida é quando a coleção original possui um indexador (como array). Para criar um código genérico com um requisito mínimo, você poderia usar IEnumerable, mas se você precisar da contagem também, então a minha maneira preferida é usar essa interface:

 public interface IEnumAndCount : IEnumerable { int Count { get; } } 

Se sua coleção original não tiver nenhum indexador, sua implementação de Contagem poderá iterar sobre a coleção, com o êxito conhecido no desempenho O (n).

Se você não quiser usar algo semelhante ao IEnumAndCount, sua melhor opção é usar o Linq.Count por razões dadas por Daniel Earwicker no topo desta questão.

Boa sorte !

Não.

Você vê essas informações disponíveis em qualquer lugar no código que você escreveu?

Você pode argumentar que o compilador pode “ver” que existem apenas dois, mas isso significaria que seria necessário analisar cada método do iterador procurando apenas por esse caso patológico específico. E mesmo se o fizesse, como você o leria, dados os limites de um IEnumerable?

Eu sugeriria chamar ToList. Sim, você está fazendo a enumeração antecipadamente, mas ainda tem access à 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 uso IEnum.ToArray().Length e funciona bem.

Eu uso esse código, se eu tiver lista de seqüências de caracteres:

 ((IList)Table).Count