Sprawdzanie, czy lista jest pusta za pomocą LINQ


122

Jaki jest „najlepszy” (biorąc pod uwagę zarówno szybkość, jak i czytelność) sposób określenia, czy lista jest pusta? Nawet jeśli lista jest typu IEnumerable<T>i nie ma właściwości Count.

W tej chwili rzucam się między to:

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

i to:

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

Domyślam się, że druga opcja jest szybsza, ponieważ wróci z wynikiem, gdy tylko zobaczy pierwszy element, podczas gdy druga opcja (dla IEnumerable) będzie musiała odwiedzić każdy element, aby zwrócić liczbę.

Biorąc to pod uwagę, czy druga opcja wygląda na tak czytelną? Które wolisz? A może możesz wymyślić lepszy sposób na przetestowanie pustej listy?

Odpowiedź Edit @ lassevk wydaje się najbardziej logiczna, w połączeniu z odrobiną sprawdzenia w czasie wykonywania, aby użyć liczby z pamięci podręcznej, jeśli to możliwe, na przykład:

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

    return !list.Any();
}

5
Znacznie bardziej lepiej nie mieszać is, a castjednak stosowanie asi nullsprawdzić:ICollection<T> collection = list as ICollection<T>; if (collection != null) return colllection.Count;
abatishchev

2
Po co pisać dodatkową metodę? Czy nie jest list.Any()równoważne list.IsEmpty? Metoda frameworka powinna zostać zoptymalizowana - warto napisać nową tylko wtedy, gdy zorientowałeś się, że jest to wąskie gardło perf.
dbkk

6
Czy ktoś zadał sobie trud mierzenia wydajności sugerowanych wdrożeń, czy też wszyscy po prostu rzucają pomysły?
Michael Brown

Zaproponowałem problem dla biblioteki klas .NET Core, która dodaje IsEmptymetodę rozszerzenia. github.com/dotnet/corefx/issues/35054 Sprawdź i zagłosuj, jeśli chcesz i zgadzasz się.
RyotaMurohoshi

Odpowiedzi:


100

Możesz to zrobić:

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

Edycja : Zwróć uwagę, że zwykłe użycie metody .Count będzie szybkie, jeśli bazowe źródło rzeczywiście ma szybką właściwość Count. Poprawną optymalizacją powyżej byłoby wykrycie kilku typów podstawowych i po prostu użycie ich właściwości .Count zamiast podejścia .Any (), ale następnie powrót do .Any (), jeśli nie można zagwarantować.


4
Lub użyj jednej linii i zwróć (source == null)? prawda:! source.Any (); (Jeśli nie rzucasz wyjątku)
Gage

1
Powiedziałbym, że tak, zgłoś wyjątek dla null, ale następnie dodaj drugą metodę rozszerzenia o nazwie IsNullOrEmpty().
devuxer,

1
public static Boolean IsNullOrEmpty <T> (to źródło IEnumerable <T>) {return source == null || ! source.Any (); }
dan

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

@ricksmt Dzięki za aktualizację! Na pewno będę tego używał!
Gage

14

Dodałbym jeden mały dodatek do kodu, na którym wydaje się, że się zdecydowałeś: sprawdź również ICollection, ponieważ jest to zaimplementowane nawet przez niektóre nieaktualne klasy ogólne (tj. Queue<T>I Stack<T>). Używałbym również aszamiast, isponieważ jest to bardziej idiomatyczne i okazało się, że jest szybsze .

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
Podoba mi się ta odpowiedź. Jednym słowem ostrzeżenia jest to, że niektóre kolekcje generują wyjątki, gdy nie implementują w pełni interfejsu, takiego jak NotSupportedExceptionlub NotImplementedException. Po raz pierwszy użyłem twojego przykładu kodu, kiedy dowiedziałem się, że kolekcja, której używałem, rzuciła wyjątek dla Count (kto wiedział ...).
Sam

1
Rozumiem, dlaczego taka optymalizacja jest przydatna w przypadku metod takich jak Count (), które muszą wyliczyć wszystkie elementy. Ale Any () musi tylko wyliczyć co najwyżej jeden element, więc nie widzę tutaj sensu. Z drugiej strony, rzutowanie i dodawane instrukcje if to stały koszt, który musisz zapłacić przy każdym połączeniu.
codymanix

8

Sam LINQ musi w jakiś sposób przeprowadzić poważną optymalizację wokół metody Count ().

Czy to cię dziwi? Wyobrażam sobie, że dla IListimplementacji, Countpo prostu odczytuje liczbę elementów bezpośrednio podczas Anymusi kwerendy IEnumerable.GetEnumeratormetody tworzenia instancji i połączenia MoveNextco najmniej raz.

/ EDYCJA @Matt:

Mogę tylko założyć, że metoda rozszerzenia Count () dla IEnumerable robi coś takiego:

Tak, oczywiście, że tak. To miałem na myśli. Właściwie używa ICollectionzamiast, IListale wynik jest taki sam.


6

Właśnie napisałem szybki test, spróbuj tego:

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

Drugi jest prawie trzykrotnie wolniejszy :)

Ponowna próba testu stopera ze stosem lub tablicą lub innymi scenariuszami naprawdę zależy od typu listy, którą się wydaje - ponieważ dowodzą, że Count jest wolniejszy.

Więc myślę, że to zależy od typu listy, której używasz!

(Aby zaznaczyć, umieściłem na liście ponad 2000 obiektów i liczenie było nadal szybsze, w przeciwieństwie do innych typów)


12
Enumerable.Count<T>()ma specjalną obsługę dla ICollection<T>. Jeśli spróbujesz tego z czymś innym niż podstawowa lista, spodziewam się, że zobaczysz znacznie inne (wolniejsze) wyniki. Any()pozostanie jednak mniej więcej taki sam.
Marc Gravell

2
Muszę zgodzić się z Markiem; to nie jest naprawdę uczciwy test.
Dan Tao

Masz jakiś pomysł, dlaczego nie ma specjalnej obsługi Enumerable.Any<T>()dla ICollection<T>? z pewnością bez parametrów Any()może po prostu sprawdzić Countwłaściwość ICollection<T>również?
Lukazoid,

5

List.Countjest O (1) zgodnie z dokumentacją Microsoft:
http://msdn.microsoft.com/en-us/library/27b47ht3.aspx

więc po prostu użyj List.Count == 0 go znacznie szybciej niż zapytanie

Dzieje się tak, ponieważ ma element członkowski danych o nazwie Count, który jest aktualizowany za każdym razem, gdy coś jest dodawane lub usuwane z listy, więc kiedy wywołujesz List.Count, nie musi iterować przez każdy element, aby go uzyskać, po prostu zwraca element członkowski danych.


1
jeśli jest to „IEnumerable”, to nie. (Na początek IEnumerable nie ma właściwości „Count”, ma metodę Count ()). Wywołanie „Count ()” będzie wymagało od IEnumerable zbadania każdego elementu na liście. Natomiast „Any” zwróci po prostu, gdy tylko znajdzie 1 element.
00jt

To zależy od źródła danych. Jeśli używasz yield do tworzenia IEnumerable, będzie musiał przejść przez IEnumerable, aby poznać jego rozmiar. W niektórych przypadkach jest to więc tylko O ​​(1). Nie zawsze jest to O (1).
TamusJRoyce

3

Druga opcja jest znacznie szybsza, jeśli masz wiele przedmiotów.

  • Any() wraca po znalezieniu 1 przedmiotu.
  • Count() musi przeglądać całą listę.

Załóżmy na przykład, że wyliczenie zawiera 1000 pozycji.

  • Any() sprawdziłby pierwszy, a następnie zwrócił true.
  • Count() zwróciłby 1000 po przejściu całego wyliczenia.

Jest to potencjalnie gorsze, jeśli używasz jednego z przesłonięć predykatu - funkcja Count () nadal musi sprawdzać każdą pojedynczą pozycję, nawet jeśli jest tylko jedno dopasowanie.

Przyzwyczaisz się do używania Any one - ma to sens i jest czytelne.

Jedno zastrzeżenie - jeśli masz List, a nie tylko IEnumerable, użyj właściwości Count tej listy.


Różnice między Any () i Count () wydają się jasne, ale kod profilowania @ crucible wydaje się wskazywać, że Count () jest szybszy w przypadku niektórych implementacji IEnumerable <T>. Dla List <T> nie mogę uzyskać Any (), aby dać szybszy wynik niż Count (), dopóki rozmiar listy nie osiągnie tysięcy elementów. Sam LINQ musi w jakiś sposób przeprowadzić poważną optymalizację wokół metody Count ().
Matt Hamilton,

3

@Konrad zaskakuje mnie to, że w moich testach przekazuję listę do metody, która akceptuje IEnumerable<T>, więc środowisko wykonawcze nie może jej zoptymalizować, wywołując metodę rozszerzenia Count () dlaIList<T> .

Mogę tylko założyć, że metoda rozszerzenia Count () dla IEnumerable robi coś takiego:

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

... innymi słowy, trochę optymalizacji czasu wykonywania dla specjalnego przypadku IList<T>.

/ EDYCJA @Konrad +1 mate - masz rację co do tego, że jest bardziej prawdopodobne, że jesteś włączony ICollection<T>.


1

Ok, a co z tym?

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

EDYCJA: Właśnie zdałem sobie sprawę, że ktoś już naszkicował to rozwiązanie. Wspomniano, że zrobi to metoda Any (), ale dlaczego nie zrobić tego samemu? pozdrowienia


3
ALE staje się mniej zwięzłe, gdy odpowiednio zamkniesz go w usingbloku, ponieważ w przeciwnym razie skonstruowałeś IDisposableobiekt, a następnie porzuciłeś. Następnie, oczywiście, stanie się bardziej zwięzły, gdy użyjesz metody rozszerzenia, która już istnieje, i po prostu zmienisz ją na return !enumerable.Any()(która robi dokładnie to).
Dan Tao

Po co przepisać już istniejącą metodę? Jak wspomniano, Any()działa dokładnie tak, więc dodanie dokładnie tej samej metody z inną nazwą będzie po prostu mylące.
Julien N

1

Inny pomysł:

if(enumerable.FirstOrDefault() != null)

Jednak bardziej podoba mi się podejście Any ().


3
A co, jeśli masz niepustą listę, w której pierwszy element jest pusty?
Ekevoo

1

Było to krytyczne, aby to działało z Entity Framework:

var genericCollection = list as ICollection<T>;

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

Jak to odpowiada na pytanie? kolekcja nie może być pusta, jeśli nie zawiera żadnych elementów.
Martin Verjans,

0

Jeśli sprawdzam za pomocą Count () Linq wykonuje "SELECT COUNT (*) .." w bazie danych, ale muszę sprawdzić, czy wyniki zawierają dane, zdecydowałem się na wprowadzenie FirstOrDefault () zamiast Count ();

Przed

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

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

Po

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

Oto moja implementacja odpowiedzi Dana Tao, uwzględniająca predykat:

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. To wszystko


1
To okropny pomysł. ToList () nie powinno być nadużywane, ponieważ wymusza to, aby wyliczalna była w pełni oceniona. Zamiast tego użyj .Any ().
Jon Rea,

-5

Ta metoda rozszerzenia działa dla mnie:

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

5
Unikaj takiego stosowania wyjątków. W powyższym kodzie spodziewasz się wyjątku dla pewnych, dobrze zdefiniowanych danych wejściowych (tj. Pustych wyliczeń). Dlatego nie ma wyjątków, są regułą. To nadużycie tego mechanizmu kontrolnego, które ma wpływ na czytelność i wydajność. Zarezerwuj użycie wyjątków dla naprawdę wyjątkowych przypadków.
Konrad Rudolph

Generalnie zgadzam się. Ale jest to obejście dla odpowiedniej brakującej metody IsEmpty. I argumentowałbym, że obejście nigdy nie jest idealnym sposobem na zrobienie czegoś ... Ponadto, szczególnie w tym przypadku, intencja jest bardzo jasna, a „brudny” kod jest zamknięty i ukryty w dobrze określonym miejscu.
Jonny Dee,

3
-1: Jeśli chcesz to zrobić w ten sposób, użyj FirstOrDefault (), jak w odpowiedzi ChulioMartinez.
Daniel Rose,

3
Obsługa wyjątków ma naprawdę słabą wydajność. To może być tutaj najgorsze rozwiązanie.
Julien N

„Wyjątki powinny być wyjątkowe”. - nie używaj ich do normalnego przebiegu programu.
Jon Rea,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.