Verificando se uma lista está vazia com LINQ

122

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

Agora eu estou jogando 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ê pode pensar em uma maneira melhor de testar uma lista vazia?

A resposta do Edit @ 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();
}
Matt Hamilton
fonte
5
Muito mais melhor não misturar ise castmas o uso ase nullverificar:ICollection<T> collection = list as ICollection<T>; if (collection != null) return colllection.Count;
abatishchev
2
Por que escrever um método extra? Não é list.Any()equivalente list.IsEmpty? O método de estrutura deve ser otimizado - vale a pena escrever um novo somente se você descobrir que é um gargalo de desempenho.
dbkk
6
Alguém se incomodou em medir o desempenho em suas implementações sugeridas ou todo mundo está apenas lançando idéias?
Michael Brown
Sugeri um problema para a biblioteca de classes do .NET Core que adiciona o IsEmptymétodo de extensão. github.com/dotnet/corefx/issues/35054 Verifique e vote se quiser e concorda.
RyotaMurohoshi

Respostas:

100

Você pode 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 o simples uso do método .Count será rápido se a fonte subjacente realmente possuir uma propriedade Count rápida. Uma otimização válida acima seria detectar alguns tipos de base e simplesmente usar a propriedade .Count desses, em vez da abordagem .Any (), mas depois voltar para .Any () se nenhuma garantia puder ser feita.

Lasse V. Karlsen
fonte
4
Ou use uma linha e retorne (source == null)? true:! source.Any (); (Se você não lançar uma exceção)
Gage
1
Eu diria que sim, lance uma exceção para null, mas adicione um segundo método de extensão chamado IsNullOrEmpty().
precisa saber é o seguinte
1
Boolean estático público IsNullOrEmpty <T> (esta fonte IEnumerable <T>) {origem de retorno == null || ! source.Any (); }
dan
1
@Gage Hoje em dia:return !source?.Any() ?? true;
ricksmt 13/09/16
@ricksmt Obrigado pela atualização! Definitivamente vou usar isso!
Gage
14

Eu faria uma pequena adição ao código no qual você parece ter se estabelecido: verifique também ICollection, pois isso também é implementado por algumas classes genéricas não obsoletas (isto é, Queue<T>e Stack<T>). Eu também usaria, em asvez de isser mais idiomático e mostrar 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();
}
Dan Tao
fonte
1
Eu gosto desta resposta. Uma palavra de aviso é que algumas coleções lançam exceções quando não implementam completamente uma interface como a NotSupportedExceptionou NotImplementedException. Eu usei seu exemplo de código pela primeira vez quando descobri que uma coleção que estava usando lançou uma exceção para o Count (quem sabia ...).
Sam
1
Entendo por que essa otimização é útil para métodos como Count (), que precisa enumerar todos os elementos. Mas Any () só precisa enumerar no máximo um elemento, então não vejo o ponto aqui. Por outro lado, os lançamentos e as declarações if que você está adicionando são um custo fixo que você deve pagar em todas as chamadas então.
Codymanix
8

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

Isso te surpreende? Eu imagino que, para IListimplementações, Countsimplesmente leia o número de elementos diretamente enquanto Anytiver que consultar o IEnumerable.GetEnumeratormétodo, criar uma instância e chamar MoveNextpelo menos uma vez.

/ EDIT @Matt:

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

Sim, claro que faz. Foi isso que eu quis dizer. Na verdade, ele usa em ICollectionvez de, IListmas o resultado é o mesmo.

Konrad Rudolph
fonte
6

Acabei de escrever um teste rápido, tente o seguinte:

 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 :)

Se você tentar novamente o cronômetro com uma pilha ou matriz ou outros cenários, isso realmente depende do tipo de lista que parece - porque eles provam que o Count é mais lento.

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

(Apenas para salientar, coloquei mais de 2000 objetos na lista e a contagem foi ainda mais rápida, ao contrário de outros tipos)

cadinho
fonte
12
Enumerable.Count<T>()tem tratamento especial para ICollection<T>. Se você tentar isso com algo diferente de uma lista básica, espero que você veja resultados significativamente mais lentos. Any()permanecerá aproximadamente o mesmo, no entanto.
Marc Gravell
2
Eu tenho que concordar com Marc; este não é um teste realmente justo.
Dan Tao
Alguma idéia de por que não há tratamento especial para Enumerable.Any<T>()para ICollection<T>? certamente o sem parâmetros Any()poderia apenas verificar a Countpropriedade ICollection<T>também?
Lukazoid
5

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

então use List.Count == 0muito 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ê o chama List.Count, não precisa percorrer todos os elementos para obtê-lo, apenas retorna o membro de dados.

Dasmowenator
fonte
1
se for um "IEnumerable", então não. (para iniciantes, IEnumerable não possui uma propriedade "Count", ele possui um método Count ().). Chamar "Count ()" exigirá que o IEnumerable examine todos os elementos da lista. Enquanto "Qualquer" retornará assim que encontrar 1 elemento.
00jt 08/08/19
Depende da fonte de dados. Se você usar yield para criar um IEnumerable, ele precisará percorrer o IEnumerable para saber seu tamanho. Portanto, é apenas O (1) em alguns casos. Nem sempre é O (1).
TamusJRoyce
3

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

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

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

  • Any() verificaria o primeiro e depois 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 todos os itens, mesmo que haja apenas uma correspondência.

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

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

Keith
fonte
As diferenças entre Any () e Count () parecem claras, mas o código de criação de perfil do @ crisol parece indicar que Count () é mais rápido para certas implementações de IEnumerable <T>. Para a Lista <T>, não consigo que Any () dê um resultado mais rápido que Count () até que o tamanho da lista suba em milhares de itens. O próprio LINQ deve estar fazendo uma otimização séria em torno do método Count () de alguma forma.
Matt Hamilton
3

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

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

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 mate - você está certo sobre a possibilidade de participar ICollection<T>.

Matt Hamilton
fonte
1

Ok, e quanto a este?

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

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

Jonny Dee
fonte
3
MAS torna-se menos sucinto quando você o coloca adequadamente em um usingbloco, pois, caso contrário, você construiu um IDisposableobjeto e o abandonou. Então, é claro, fica mais sucinto quando você utiliza o método de extensão que já existe e apenas o altera para return !enumerable.Any()(o que faz exatamente isso).
Dan Tao
Por que reescrever um método já existente? Conforme mencionado, Any()executa exatamente isso, portanto, adicionar exatamente o mesmo método com outro nome será apenas confuso.
Jul Jul N
1

Outra ideia:

if(enumerable.FirstOrDefault() != null)

No entanto, eu gosto mais da abordagem Any ().

ChulioMartinez
fonte
3
E se você tiver uma lista não vazia na qual o primeiro elemento é nulo?
Ekevoo
1

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

var genericCollection = list as ICollection<T>;

if (genericCollection != null)
{
   //your code 
}
Holt Mansfield
fonte
Como isso responde à pergunta? a coleção não pode ser nula sem ter elementos nela.
Martin Verjans
0

Se eu verificar com Count (), o Linq executa uma "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 de

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

var itemCfop = cfop.FirstOrDefault();

if (itemCfop != null)
{
    //....
}
Gandarez
fonte
0
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);
    }
suneelsarraf
fonte
0

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;
}
devuxer
fonte
-1
List<T> li = new List<T>();
(li.First().DefaultValue.HasValue) ? string.Format("{0:yyyy/MM/dd}", sender.First().DefaultValue.Value) : string.Empty;
Milad Sadeghi
fonte
-3

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

user3149517
fonte
1
Esta é uma péssima ideia. ToList () não deve ser usado em excesso, pois força o enumerável a ser totalmente avaliado. Use .Any () em vez disso.
Jon Rea
-5

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;
    }
}
Jonny Dee
fonte
5
Evite o uso de exceções. No código acima, você espera uma exceção para determinadas entradas bem definidas (ou seja, enumerações vazias). Portanto, eles não são exceções, são a regra. Isso é um abuso desse mecanismo de controle que tem implicações na legibilidade e no desempenho. Reserve o uso de exceções para casos verdadeiramente excepcionais.
21978 Konrad Rudolph
Geralmente, eu concordo. Mas esta é uma solução alternativa para o correspondente método IsEmpty ausente. E eu argumentaria que uma solução alternativa nunca é a maneira ideal de fazer algo ... Além disso, especialmente neste caso, a intenção é muito clara e o código "sujo" é encapsulado e oculto em um local bem definido.
21910 Jonny Dee
3
-1: Se você quiser fazer dessa maneira, use FirstOrDefault (), como na resposta de ChulioMartinez.
Daniel Rose
3
O tratamento de exceções tem uma eficiência de desempenho realmente ruim. Portanto, essa pode ser a pior solução aqui.
Jul Jul N
"Exceções devem ser excepcionais." - não os use para o fluxo normal do programa.
Jon Rea