Verifica se un elenco è vuoto con LINQ


122

Qual è il modo "migliore" (tenendo conto sia della velocità che della leggibilità) per determinare se un elenco è vuoto? Anche se l'elenco è di tipo IEnumerable<T>e non ha una proprietà Count.

In questo momento sto vomitando tra questo:

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

e questo:

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

La mia ipotesi è che la seconda opzione sia più veloce, poiché tornerà con un risultato non appena vedrà il primo elemento, mentre la seconda opzione (per un IEnumerable) dovrà visitare ogni elemento per restituire il conteggio.

Detto questo, la seconda opzione ti sembra leggibile? Quale preferiresti? O puoi pensare a un modo migliore per testare un elenco vuoto?

La risposta di Edit @ lassevk sembra essere la più logica, unita a un po 'di controllo del runtime per utilizzare un conteggio memorizzato nella cache se possibile, come questo:

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

    return !list.Any();
}

5
Molto di più meglio non mescolare ise castma usare ase nullcontrollare:ICollection<T> collection = list as ICollection<T>; if (collection != null) return colllection.Count;
abatishchev

2
Perché scrivere un metodo aggiuntivo? Non è list.Any()equivalente a list.IsEmpty? Il metodo del framework dovrebbe essere ottimizzato: vale la pena scriverne uno nuovo solo se hai capito che è un collo di bottiglia delle prestazioni.
dbkk

6
Qualcuno si è preso la briga di misurare le prestazioni sulle implementazioni suggerite o tutti hanno semplicemente buttato fuori idee?
Michael Brown

Ho suggerito il problema alla libreria di classi .NET Core che aggiunge il IsEmptymetodo di estensione. github.com/dotnet/corefx/issues/35054 Per favore controlla e votalo se vuoi e sei d'accordo.
RyotaMurohoshi

Risposte:


100

Potresti farlo:

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

Modifica : nota che il semplice utilizzo del metodo .Count sarà veloce se la fonte sottostante ha effettivamente una proprietà Count veloce. Un'ottimizzazione valida sopra sarebbe quella di rilevare alcuni tipi di base e utilizzare semplicemente la proprietà .Count di quelli, invece dell'approccio .Any (), ma poi tornare a .Any () se non è possibile fornire alcuna garanzia.


4
Oppure usa una riga e restituisci (source == null)? vero:! source.Any (); (Se non stai lanciando un'eccezione)
Gage

1
Direi, sì, lancia un'eccezione per null, ma poi aggiungi un secondo metodo di estensione chiamato IsNullOrEmpty().
devuxer

1
public static Boolean IsNullOrEmpty <T> (questa sorgente IEnumerable <T>) {return source == null || ! source.Any (); }
dan

1
@Gage Nowadays:return !source?.Any() ?? true;
ricksmt

@ricksmt Grazie per l'aggiornamento! Lo userò sicuramente!
Gage

14

Vorrei fare una piccola aggiunta al codice su cui sembri esserti stabilito: controlla anche per ICollection, poiché questo è implementato anche da alcune classi generiche non obsolete (cioè, Queue<T>e Stack<T>). Lo userei anche al asposto di isperché è più idiomatico e ha dimostrato di essere più veloce .

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

1
Mi piace questa risposta. Un avvertimento è che alcune raccolte generano eccezioni quando non implementano completamente un'interfaccia come NotSupportedExceptiono NotImplementedException. Ho usato per la prima volta il tuo esempio di codice quando ho scoperto che una raccolta che stavo usando ha generato un'eccezione per Count (chi lo sapeva ...).
Sam

1
Capisco perché una tale ottimizzazione è utile per metodi come Count () che deve enumerare tutti gli elementi. Ma Any () ha solo bisogno di enumerare al massimo un elemento, quindi non riesco a vedere il punto qui. D'altra parte, i cast e le istruzioni if ​​che stai aggiungendo sono un costo fisso che devi pagare per ogni chiamata in quel momento.
codymanix

8

LINQ stesso deve in qualche modo eseguire una seria ottimizzazione attorno al metodo Count ().

Questo ti sorprende? Immagino che per le IListimplementazioni, Countlegga semplicemente il numero di elementi direttamente mentre Anydeve interrogare il IEnumerable.GetEnumeratormetodo, creare un'istanza e chiamare MoveNextalmeno una volta.

/ EDIT @Matt:

Posso solo presumere che il metodo di estensione Count () per IEnumerable stia facendo qualcosa del genere:

Sì, certo che lo fa. Questo è quello che volevo dire. In realtà, usa ICollectioninvece di IListma il risultato è lo stesso.


6

Ho appena scritto un breve test, prova questo:

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

Il secondo è quasi tre volte più lento :)

Provare di nuovo il test del cronometro con uno Stack o un array o altri scenari dipende davvero dal tipo di elenco che sembra, perché dimostrano che Count è più lento.

Quindi immagino che dipenda dal tipo di elenco che stai usando!

(Giusto per sottolineare, ho inserito più di 2000 oggetti nell'elenco e il conteggio è stato ancora più veloce, al contrario di altri tipi)


12
Enumerable.Count<T>()ha una gestione speciale per ICollection<T>. Se lo provi con qualcosa di diverso da un elenco di base, mi aspetto che vedrai risultati significativamente diversi (più lenti). Any()rimarrà più o meno lo stesso, però.
Marc Gravell

2
Devo concordare con Marc; questo non è un test davvero equo.
Dan Tao

Qualche idea sul perché non ci sia una gestione speciale Enumerable.Any<T>()per ICollection<T>? sicuramente il senza parametri Any()potrebbe controllare anche la Countproprietà ICollection<T>?
Lukazoid

5

List.Countè O (1) secondo la documentazione di Microsoft:
http://msdn.microsoft.com/en-us/library/27b47ht3.aspx

quindi usalo List.Count == 0molto più velocemente di una query

Questo perché ha un membro dati chiamato Count che viene aggiornato ogni volta che qualcosa viene aggiunto o rimosso dall'elenco, quindi quando lo chiami List.Countnon è necessario iterare attraverso ogni elemento per ottenerlo, restituisce solo il membro dati.


1
se è un "IEnumerable" allora no. (per i principianti IEnumerable non ha una proprietà "Count", ha un metodo Count ().). La chiamata "Count ()" richiederà a IEnumerable di esaminare ogni singolo elemento nell'elenco. Mentre "Qualsiasi" tornerà appena trova 1 elemento.
00jt

Dipende dall'origine dati. Se usi yield per creare un IEnumerable, dovrà attraversare IEnumerable per conoscerne le dimensioni. Quindi è solo O (1) in alcuni casi. Non è sempre O (1).
TamusJRoyce

3

La seconda opzione è molto più rapida se hai più oggetti.

  • Any() restituisce non appena viene trovato 1 articolo.
  • Count() deve continuare a scorrere l'intero elenco.

Ad esempio, supponiamo che l'enumerazione contenga 1000 elementi.

  • Any() controlla il primo, quindi restituisce true.
  • Count() restituirebbe 1000 dopo aver attraversato l'intera enumerazione.

Questo è potenzialmente peggiore se si utilizza uno degli override del predicato: Count () deve comunque controllare ogni singolo elemento, anche se c'è solo una corrispondenza.

Ti abitui a usare Any one: ha senso ed è leggibile.

Un avvertimento: se si dispone di un elenco, anziché solo di un IEnumerable, utilizzare la proprietà Count di tale elenco.


Le differenze tra Any () e Count () sembrano chiare, ma il codice di profilazione di @ crucible sembra indicare che Count () è più veloce per alcune implementazioni di IEnumerable <T>. Per List <T> non riesco a far sì che Any () dia un risultato più veloce di Count () fino a quando la dimensione dell'elenco non supera le migliaia di elementi. LINQ stesso deve in qualche modo fare una seria ottimizzazione attorno al metodo Count ().
Matt Hamilton

3

@Konrad quello che mi sorprende è che nei miei test, sto passando l'elenco a un metodo che accetta IEnumerable<T>, quindi il runtime non può ottimizzarlo chiamando il metodo di estensione Count () per IList<T>.

Posso solo presumere che il metodo di estensione Count () per IEnumerable stia facendo qualcosa del genere:

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

... in altre parole, un po 'di ottimizzazione del runtime per il caso speciale di IList<T>.

/ EDIT @Konrad +1 mate - hai ragione sul fatto che è più probabile che sia su ICollection<T>.


1

Ok, allora che ne dici di questo?

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

EDIT: mi sono appena reso conto che qualcuno ha già abbozzato questa soluzione. È stato detto che il metodo Any () lo farà, ma perché non farlo da soli? Saluti


3
MA diventa meno succinto quando lo racchiudi correttamente in un usingblocco poiché altrimenti hai costruito un IDisposableoggetto e poi lo hai abbandonato. Quindi, ovviamente, diventa più succinto quando si utilizza il metodo di estensione che è già presente e lo si cambia in return !enumerable.Any()(che fa esattamente questo).
Dan Tao

Perché riscrivere un metodo già esistente? Come accennato, Any()esegue esattamente questo, quindi aggiungere esattamente lo stesso metodo con un altro nome creerà confusione.
Julien N

1

Un'altra idea:

if(enumerable.FirstOrDefault() != null)

Tuttavia mi piace di più l'approccio Any ().


3
Cosa succede se hai un elenco non vuoto in cui il primo elemento è nullo?
Ekevoo

1

Questo era fondamentale per farlo funzionare con Entity Framework:

var genericCollection = list as ICollection<T>;

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

Come risponde alla domanda? la collezione non può essere nulla pur non avendo elementi al suo interno.
Martin Verjans

0

Se controllo con Count (), Linq esegue un "SELECT COUNT (*) .." nel database, ma devo controllare se i risultati contengono dati, ho deciso di introdurre FirstOrDefault () invece di Count ();

Prima

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

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

Dopo

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

var itemCfop = cfop.FirstOrDefault();

if (itemCfop != null)
{
    //....
}

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

0

Ecco la mia implementazione della risposta di Dan Tao, consentendo un predicato:

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

-1
List<T> li = new List<T>();
(li.First().DefaultValue.HasValue) ? string.Format("{0:yyyy/MM/dd}", sender.First().DefaultValue.Value) : string.Empty;

-3

myList.ToList().Count == 0. È tutto


1
Questa è un'idea orribile. ToList () non dovrebbe essere usato in modo eccessivo, poiché forza l'enumerabile a essere completamente valutato. Usa .Any () invece.
Jon Rea,

-5

Questo metodo di estensione funziona per me:

public static bool IsEmpty<T>(this IEnumerable<T> enumerable)
{
    try
    {
        enumerable.First();
        return false;
    }
    catch (InvalidOperationException)
    {
        return true;
    }
}

5
Evita tale uso di eccezioni. Nel codice precedente, ci si aspetta un'eccezione per determinati input ben definiti (ad esempio enumerazioni vuote). Quindi, non sono eccezioni, sono la regola. Questo è un abuso di questo meccanismo di controllo che ha implicazioni sulla leggibilità e sulle prestazioni. Riservare l'uso delle eccezioni per casi veramente eccezionali.
Konrad Rudolph,

In generale, sono d'accordo. Ma questa è una soluzione alternativa per un metodo IsEmpty mancante corrispondente. E direi che una soluzione alternativa non è mai il modo ideale per fare qualcosa ... Inoltre, soprattutto in questo caso, l'intento è molto chiaro e il codice "sporco" è incapsulato e nascosto in un luogo ben definito.
Jonny Dee

3
-1: Se vuoi farlo in questo modo, usa FirstOrDefault (), come nella risposta di ChulioMartinez.
Daniel Rose

3
La gestione delle eccezioni ha un'efficienza delle prestazioni davvero scarsa. Quindi questa potrebbe essere la soluzione peggiore qui.
Julien N

"Le eccezioni dovrebbero essere eccezionali." - non utilizzarli per il normale flusso del programma.
Jon Rea,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.