Pergunta

Qual é a maneira "melhor" (levando em consideração a velocidade e a legibilidade) de determinar se uma lista está vazia?Mesmo que a lista seja do tipo IEnumerable<T> e não possui uma propriedade Count.

No momento estou pensando entre isso:

if (myList.Count() == 0) { ... }

e isto:

if (!myList.Any()) { ... }

Meu palpite é que a segunda opção é mais rápida, pois retornará com um resultado assim que vir o primeiro item, enquanto a segunda opção (para um IEnumerable) precisará visitar todos os itens para retornar a contagem.

Dito isto, a segunda opção parece tão legível para você?Qual você prefere?Ou você consegue pensar em uma maneira melhor de testar uma lista vazia?

Editar A resposta de @lassevk parece ser a mais lógica, juntamente com um pouco de verificação de tempo de execução para usar uma contagem em cache, se possível, assim:

public static bool IsEmpty<T>(this IEnumerable<T> list)
{
    if (list is ICollection<T>) return ((ICollection<T>)list).Count == 0;

    return !list.Any();
}
Foi útil?

Solução

Você poderia fazer isso:

public static Boolean IsEmpty<T>(this IEnumerable<T> source)
{
    if (source == null)
        return true; // or throw an exception
    return !source.Any();
}

Editar:Observe que simplesmente usar o método .Count será rápido se a fonte subjacente realmente tiver uma propriedade Count rápida.Uma otimização válida acima seria detectar alguns tipos base e simplesmente usar a propriedade .Count deles, em vez da abordagem .Any(), mas depois voltar para .Any() se nenhuma garantia puder ser feita.

Outras dicas

Eu faria uma pequena adição ao código que você parece ter escolhido:verifique também ICollection, já que isso também é implementado por algumas classes genéricas não obsoletas (ou seja, Queue<T> e Stack<T>).eu também usaria as em vez de is já que é mais idiomático e demonstrou ser mais rápido.

public static bool IsEmpty<T>(this IEnumerable<T> list)
{
    if (list == null)
    {
        throw new ArgumentNullException("list");
    }

    var genericCollection = list as ICollection<T>;
    if (genericCollection != null)
    {
        return genericCollection.Count == 0;
    }

    var nonGenericCollection = list as ICollection;
    if (nonGenericCollection != null)
    {
        return nonGenericCollection.Count == 0;
    }

    return !list.Any();
}

O próprio LINQ deve estar fazendo alguma otimização séria em torno do método Count() de alguma forma.

Isso te surpreende?Imagino isso por IList implementações, Count simplesmente lê o número de elementos diretamente enquanto Any tem que consultar o IEnumerable.GetEnumerator método, crie uma instância e chame MoveNext pelo menos uma vez.

/EDITAR @Matt:

Só posso assumir que o método de extensão Count() para IEnumerable está fazendo algo assim:

Sim, claro que faz.Isso é o que eu quis dizer.Na verdade, ele usa ICollection em vez de IList Mas o resultado é o mesmo.

Acabei de escrever um teste rápido, tente isto:

 IEnumerable<Object> myList = new List<Object>();

 Stopwatch watch = new Stopwatch();

 int x;

 watch.Start();
 for (var i = 0; i <= 1000000; i++)
 {
    if (myList.Count() == 0) x = i; 
 }
 watch.Stop();

 Stopwatch watch2 = new Stopwatch();

 watch2.Start();
 for (var i = 0; i <= 1000000; i++)
 {
     if (!myList.Any()) x = i;
 }
 watch2.Stop();

 Console.WriteLine("myList.Count() = " + watch.ElapsedMilliseconds.ToString());
 Console.WriteLine("myList.Any() = " + watch2.ElapsedMilliseconds.ToString());
 Console.ReadLine();

O segundo é quase três vezes mais lento :)

Tentar o teste do cronômetro novamente com uma pilha ou array ou outros cenários realmente depende do tipo de lista que parece - porque eles provam que o Count é mais lento.

Então acho que depende do tipo de lista que você está usando!

(Só para ressaltar, coloquei mais de 2.000 objetos na Lista e a contagem foi ainda mais rápida, ao contrário de outros tipos)

List.Count é O(1) de acordo com a documentação da Microsoft:
http://msdn.microsoft.com/en-us/library/27b47ht3.aspx

então é só usar List.Count == 0 é muito mais rápido que uma consulta

Isso ocorre porque ele possui um membro de dados chamado Count que é atualizado sempre que algo é adicionado ou removido da lista, portanto, quando você chama List.Count não é necessário percorrer todos os elementos para obtê-lo, apenas retorna o membro de dados.

A segunda opção é muito mais rápida se você tiver vários itens.

  • Any() retorna assim que 1 item for encontrado.
  • Count() tem que continuar percorrendo a lista inteira.

Por exemplo, suponha que a enumeração tenha 1.000 itens.

  • Any() verificaria o primeiro e retornaria verdadeiro.
  • Count() retornaria 1000 depois de percorrer toda a enumeração.

Isso é potencialmente pior se você usar uma das substituições de predicado - Count() ainda precisa verificar cada item, mesmo que haja apenas uma correspondência.

Você se acostuma a usar Any - faz sentido e é legível.

Uma ressalva: se você tiver uma lista, em vez de apenas um IEnumerable, use a propriedade Count dessa lista.

@Konrad o que me surpreende é que em meus testes estou passando a lista para um método que aceita IEnumerable<T>, então o tempo de execução não pode otimizá-lo chamando o método de extensão Count() para IList<T>.

Só posso assumir que o método de extensão Count() para IEnumerable está fazendo algo assim:

public static int Count<T>(this IEnumerable<T> list)
{
    if (list is IList<T>) return ((IList<T>)list).Count;

    int i = 0;
    foreach (var t in list) i++;
    return i;
}

...em outras palavras, um pouco de otimização de tempo de execução para o caso especial de IList<T>.

/EDIT @Konrad +1 companheiro - você está certo sobre é mais provável que esteja ativado ICollection<T>.

Ok, e quanto a este?

public static bool IsEmpty<T>(this IEnumerable<T> enumerable)
{
    return !enumerable.GetEnumerator().MoveNext();
}

EDITAR:Acabei de perceber que alguém já esboçou essa solução.Foi mencionado que o método Any() fará isso, mas por que não fazer você mesmo?Cumprimentos

Outra ideia:

if(enumerable.FirstOrDefault() != null)

No entanto, gosto mais da abordagem Any().

Isso foi fundamental para que isso funcionasse com o Entity Framework:

var genericCollection = list as ICollection<T>;

if (genericCollection != null)
{
   //your code 
}

Se eu verificar com Count() o Linq executa um "SELECT COUNT(*).." no banco de dados, mas preciso verificar se os resultados contêm dados, resolvi introduzir FirstOrDefault() em vez de Count();

Antes

var cfop = from tabelaCFOPs in ERPDAOManager.GetTable<TabelaCFOPs>()

if (cfop.Count() > 0)
{
    var itemCfop = cfop.First();
    //....
}

Depois

var cfop = from tabelaCFOPs in ERPDAOManager.GetTable<TabelaCFOPs>()

var itemCfop = cfop.FirstOrDefault();

if (itemCfop != null)
{
    //....
}
private bool NullTest<T>(T[] list, string attribute)

    {
        bool status = false;
        if (list != null)
        {
            int flag = 0;
            var property = GetProperty(list.FirstOrDefault(), attribute);
            foreach (T obj in list)
            {
                if (property.GetValue(obj, null) == null)
                    flag++;
            }
            status = flag == 0 ? true : false;
        }
        return status;
    }


public PropertyInfo GetProperty<T>(T obj, string str)

    {
        Expression<Func<T, string, PropertyInfo>> GetProperty = (TypeObj, Column) => TypeObj.GetType().GetProperty(TypeObj
            .GetType().GetProperties().ToList()
            .Find(property => property.Name
            .ToLower() == Column
            .ToLower()).Name.ToString());
        return GetProperty.Compile()(obj, str);
    }

Aqui está minha implementação da resposta de Dan Tao, permitindo um predicado:

public static bool IsEmpty<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null) throw new ArgumentNullException();
    if (IsCollectionAndEmpty(source)) return true;
    return !source.Any(predicate);
}

public static bool IsEmpty<TSource>(this IEnumerable<TSource> source)
{
    if (source == null) throw new ArgumentNullException();
    if (IsCollectionAndEmpty(source)) return true;
    return !source.Any();
}

private static bool IsCollectionAndEmpty<TSource>(IEnumerable<TSource> source)
{
    var genericCollection = source as ICollection<TSource>;
    if (genericCollection != null) return genericCollection.Count == 0;
    var nonGenericCollection = source as ICollection;
    if (nonGenericCollection != null) return nonGenericCollection.Count == 0;
    return false;
}
List<T> li = new List<T>();
(li.First().DefaultValue.HasValue) ? string.Format("{0:yyyy/MM/dd}", sender.First().DefaultValue.Value) : string.Empty;

myList.ToList().Count == 0.Isso é tudo

Este método de extensão funciona para mim:

public static bool IsEmpty<T>(this IEnumerable<T> enumerable)
{
    try
    {
        enumerable.First();
        return false;
    }
    catch (InvalidOperationException)
    {
        return true;
    }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top