Pergunta

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

Vamos dizer que eu quero iterar sobre aqueles e escrever algo como processamento #n de # m.

Existe uma maneira que eu possa descobrir o valor de m sem iteração antes da minha iteração principal?

Espero que eu fiz claro.

Foi útil?

Solução

IEnumerable não suporta isso. Isso ocorre por design. IEnumerable usa avaliação preguiçosa para obter os elementos que você pedir apenas antes de precisar deles.

Se você quer saber o número de itens sem a iteração sobre eles você pode usar ICollection<T>, ele tem uma propriedade Count.

Outras dicas

O método de extensão System.Linq.Enumerable.Count em IEnumerable<T> tem a seguinte aplicaçã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;

Assim, ele tenta elenco para ICollection<T>, que tem uma propriedade Count e usos que se possível. Caso contrário, ele repete.

Então, sua melhor aposta é usar o método de extensão Count() em seu objeto IEnumerable<T>, como você vai obter o melhor desempenho possível dessa forma.

Basta adicionar extra de algumas informações:

A extensão Count() nem sempre iterate. Considere LINQ to SQL, onde a contagem vai para o banco de dados, mas em vez de trazer de volta todas as linhas, ele emite o comando Count() SQL e retornos que resultam em seu lugar.

Além disso, o compilador (ou tempo de execução) é suficiente inteligente que ele irá chamar o método objetos Count() se ele tiver um. Por isso é não como outros respondedores dizer que, sendo completamente ignorante e sempre repetindo para contar elementos.

Em muitos casos em que 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 avaliação preguiçosa do LINQ, pois ele pode curto-circuito, uma vez que pode determinar existem quaisquer elementos. É também mais legível

Um amigo meu tem uma série de posts que fornecem uma ilustração para que você não pode fazer isso. Ele cria função que retorna um IEnumerable, onde cada iteração retorna o próximo número primo, todo o caminho até ulong.MaxValue, e o próximo item não é calculado até que você perguntar para ele. Rápido, pergunta pop: quantos itens são devolvidos

Aqui estão as mensagens, mas eles são tipo de muito tempo:

  1. Além Loops (fornece uma classe EnumerableUtility inicial utilizado nos outros lugares)
  2. Aplicações de Iterate (implementação inicial)
  3. loucos de Extensão Métodos: ToLazyList (otimizações de desempenho)

IEnumerable não pode contar, sem iteração.

Em circunstâncias "normais", seria possível para as classes que implementam IEnumerable ou IEnumerable , como List , para implementar o método de contagem, devolvendo o List .Count propriedade. No entanto, o método de contagem não é realmente um método definido na IEnumerable ou a interface IEnumerable. (O único que é, na verdade, é GetEnumerator.) E isso significa que uma implementação específica de classe não podem ser previstas para o efeito.

Em vez disso, o conde é um método de extensão, definido na classe estática Enumerable. Isso significa que ele pode ser chamado em qualquer instância de um IEnumerable classe derivada, independentemente da implementação dessa classe. Mas isso também significa que é implementada em um único lugar, externa a qualquer uma dessas classes. Qual dos meios do curso que deve ser implementado de uma maneira que é completamente independente dessas internos alunos. A única maneira de fazer a contagem é através de iteração.

Como alternativa, você pode fazer o seguinte:

Tables.ToList<string>().Count;

Não, não em geral. Um ponto em usar enumerables é que o conjunto real de objetos na enumeração não é conhecido (com antecedência, ou mesmo em tudo).

Você pode usar 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ê vai obter o resultado '2'.

Indo além da sua pergunta imediata (que foi completamente respondida pela negativa), se você estiver olhando para relatório de progresso enquanto o processamento de um enumeráveis, que você pode querer olhar para o meu blog Relatório de Progresso Durante Linq consultas .

Ele permite que você faça o seguinte:

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 tal forma dentro de um método para verificar o passado em conteúdo IEnumberable

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

}

Isso depende de qual versão do .Net e implementação do seu objeto de IEnumerable. Microsoft fixou o método IEnumerable.Count para verificar a implementação, e usa o ICollection.Count ou ICollection .Count, veja detalhes aqui https://connect.microsoft.com/VisualStudio/feedback/details/454130

E abaixo é o MSIL de Ildasm para System.Core, no qual reside o 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

Aqui está uma grande discussão sobre avaliação preguiçosa e adiada execução . Basicamente você tem que materializar a lista para obter esse valor.

Resultado da função IEnumerable.Count () pode estar errado. Este é um exemplo muito simples de teste:

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());
    }
  }
}

Resultado deve ser (7,7,3,3), mas resultado real é (7,7,3,17)

A única maneira de ter uma contagem rápida é quando a coleção original tem um indexador (como matriz). A fim de criar um código genérico com um requisito mínimo que você poderia usar IEnumerable mas se você precisa a contagem também, em seguida, a minha maneira preferida é usar essa interface:


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

Se o seu acervo original não tem qualquer indexador, a sua implementação Conde poderia iteração sobre a coleção, com o hit conhecido no desempenho O (n).

Se você não quiser usar algo semelhante ao IEnumAndCount, sua melhor aposta é ir com Linq.Count por razões dadas por Daniel Earwicker perto do topo desta questão.

Boa sorte!

Não.

Você vê que a informação disponível em qualquer lugar no código que você escreveu?

Você pode argumentar que o compilador pode "ver" que há apenas dois, mas isso significaria que seria necessário analisar cada método iterator olhando apenas para esse caso patológico específico. E mesmo que o fizesse, como você lê-lo, dados os limites de um IEnumerable?

Gostaria de sugerir chamando ToList. Sim, você está fazendo a enumeração cedo, mas você ainda tem acesso a sua lista de itens.

Pode não produzir o melhor desempenho, mas você pode usar LINQ para contar os elementos de um IEnumerable:

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

Eu uso IEnum<string>.ToArray<string>().Length e trabalha muito bem.

Eu uso esse código, se eu tiver lista de strings:

((IList<string>)Table).Count
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top