Überprüfen, ob eine Liste mit LINQ leer ist

122

Was ist der "beste" Weg (unter Berücksichtigung von Geschwindigkeit und Lesbarkeit), um festzustellen, ob eine Liste leer ist? Auch wenn die Liste vom Typ ist IEnumerable<T>und keine Count-Eigenschaft hat.

Im Moment werfe ich dazwischen:

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

und das:

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

Ich vermute, dass die zweite Option schneller ist, da sie ein Ergebnis liefert, sobald das erste Element angezeigt wird, während die zweite Option (für eine IEnumerable) jedes Element besuchen muss, um die Anzahl zurückzugeben.

Abgesehen davon, erscheint Ihnen die zweite Option als lesbar? Was würdest du bevorzugen? Oder können Sie sich einen besseren Weg vorstellen, um eine leere Liste zu testen?

Die Antwort von Edit @ lassevk scheint die logischste zu sein, verbunden mit ein wenig Laufzeitprüfung, um wenn möglich eine zwischengespeicherte Anzahl zu verwenden, wie folgt:

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
quelle
5
Viel besser, nicht mischen isund castsondern verwenden asund nullprüfen:ICollection<T> collection = list as ICollection<T>; if (collection != null) return colllection.Count;
abatishchev
2
Warum eine zusätzliche Methode schreiben? Ist das nicht list.Any()gleichbedeutend mit list.IsEmpty? Die Framework-Methode sollte optimiert werden - es lohnt sich nur, eine neue zu schreiben, wenn Sie herausgefunden haben, dass es sich um einen Perf-Engpass handelt.
dbkk
6
Hat sich jemand die Mühe gemacht, die Leistung an den vorgeschlagenen Implementierungen zu messen, oder werfen alle nur Ideen aus?
Michael Brown
Ich habe ein Problem für die .NET Core-Klassenbibliothek vorgeschlagen, die eine IsEmptyErweiterungsmethode hinzufügt . github.com/dotnet/corefx/issues/35054 Bitte überprüfen und stimmen Sie ab, wenn Sie möchten und zustimmen.
RyotaMurohoshi

Antworten:

100

Sie könnten dies tun:

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

Bearbeiten : Beachten Sie, dass die einfache Verwendung der .Count-Methode schnell ist, wenn die zugrunde liegende Quelle tatsächlich über eine Fast Count-Eigenschaft verfügt. Eine gültige Optimierung oben wäre, einige Basistypen zu erkennen und einfach die .Count-Eigenschaft dieser anstelle des .Any () -Ansatzes zu verwenden, dann aber auf .Any () zurückzugreifen, wenn keine Garantie gegeben werden kann.

Lasse V. Karlsen
quelle
4
Oder verwenden Sie eine Zeile und geben Sie zurück (source == null)? true :! source.Any (); (Wenn Sie keine Ausnahme werfen)
Gage
1
Ich würde sagen, ja, eine Ausnahme für null auslösen, dann aber eine zweite Erweiterungsmethode hinzufügen, die aufgerufen wird IsNullOrEmpty().
Devuxer
1
public static Boolean IsNullOrEmpty <T> (diese IEnumerable <T> -Quelle) {return source == null || ! source.Any (); }
Dan
1
@Gage Heutzutage:return !source?.Any() ?? true;
Ricksmt
@ricksmt Danke für das Update! Ich werde das auf jeden Fall nutzen!
Gage
14

Ich würde eine kleine Ergänzung zu dem Code machen, auf den Sie sich anscheinend festgelegt haben: Überprüfen Sie auch ICollection, ob dies auch von einigen nicht veralteten generischen Klassen implementiert wird (dh Queue<T>und Stack<T>). Ich würde auch asanstelle von verwenden, isda es idiomatischer ist und sich als schneller erwiesen hat .

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
quelle
1
Ich mag diese Antwort. Ein Wort der Warnung ist, dass einige Sammlungen Ausnahmen auslösen, wenn sie eine Schnittstelle wie NotSupportedExceptionoder nicht vollständig implementieren NotImplementedException. Ich habe Ihr Codebeispiel zum ersten Mal verwendet, als ich herausfand, dass eine von mir verwendete Sammlung eine Ausnahme für Count auslöste (wer wusste ...).
Sam
1
Ich verstehe, warum eine solche Optimierung für Methoden wie Count () nützlich ist, die alle Elemente aufzählen müssen. Aber Any () muss höchstens ein Element aufzählen, daher verstehe ich den Punkt hier nicht. Auf der anderen Seite sind die Casts und die If-Anweisungen, die Sie hinzufügen, feste Kosten, die Sie dann bei jedem Anruf bezahlen müssen.
Codymanix
8

LINQ selbst muss die Count () -Methode irgendwie ernsthaft optimieren.

Überrascht dich das? Ich stelle mir vor, dass bei IListImplementierungen Counteinfach die Anzahl der Elemente direkt gelesen wird, während Anydie IEnumerable.GetEnumeratorMethode abgefragt , eine Instanz erstellt und MoveNextmindestens einmal aufgerufen werden muss.

/ EDIT @Matt:

Ich kann nur davon ausgehen, dass die Count () - Erweiterungsmethode für IEnumerable ungefähr so ​​funktioniert:

Ja, natürlich. Das habe ich gemeint. Eigentlich wird es ICollectionanstelle von verwendet, IListaber das Ergebnis ist das gleiche.

Konrad Rudolph
quelle
6

Ich habe gerade einen kurzen Test geschrieben. Versuchen Sie Folgendes:

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

Der zweite ist fast dreimal langsamer :)

Wenn Sie den Stoppuhr-Test erneut mit einem Stack oder Array oder anderen Szenarien versuchen, hängt dies wirklich von der Art der Liste ab, die es scheint - denn sie beweisen, dass Count langsamer ist.

Ich denke, es hängt von der Art der Liste ab, die Sie verwenden!

(Nur um darauf hinzuweisen, ich habe mehr als 2000 Objekte in die Liste aufgenommen und die Zählung war immer noch schneller, im Gegensatz zu anderen Typen)

Tiegel
quelle
12
Enumerable.Count<T>()hat spezielle Handhabung für ICollection<T>. Wenn Sie dies mit etwas anderem als einer Basisliste versuchen , werden Sie wahrscheinlich signifikant unterschiedliche (langsamere) Ergebnisse sehen. Any()wird aber ungefähr gleich bleiben.
Marc Gravell
2
Ich muss Marc zustimmen; Dies ist kein wirklich fairer Test.
Dan Tao
Irgendeine Idee, warum es kein spezielles Handling Enumerable.Any<T>()für gibt ICollection<T>? Sicherlich könnte der Parameterlose Any()auch die CountEigenschaft überprüfen ICollection<T>?
Lukazoid
5

List.Countist O (1) gemäß der Dokumentation von Microsoft:
http://msdn.microsoft.com/en-us/library/27b47ht3.aspx

Verwenden Sie List.Count == 0es einfach viel schneller als eine Abfrage

Dies liegt daran, dass es ein Datenelement namens Count hat, das jedes Mal aktualisiert wird, wenn etwas zur Liste hinzugefügt oder daraus entfernt wird. Wenn Sie also aufrufen List.Count, muss es nicht jedes Element durchlaufen, um es abzurufen, sondern gibt nur das Datenelement zurück.

Dasmowenator
quelle
1
Wenn es ein "IEnumerable" ist, dann nein. (Für den Anfang hat IEnumerable keine "Count" -Eigenschaft, sondern eine Count () -Methode.) Für den Aufruf von "Count ()" muss IEnumerable jedes einzelne Element in der Liste untersuchen. Während "Any" nur zurückkehrt, sobald es 1 Element findet.
00jt
Dies hängt von der Datenquelle ab. Wenn Sie Yield verwenden, um eine IEnumerable zu erstellen, muss diese die IEnumerable durchlaufen, um ihre Größe zu ermitteln. In einigen Fällen ist es also nur O (1). Es ist nicht immer O (1).
TamusJRoyce
3

Die zweite Option ist viel schneller, wenn Sie mehrere Elemente haben.

  • Any() kehrt zurück, sobald 1 Artikel gefunden wurde.
  • Count() muss die gesamte Liste durchgehen.

Angenommen, die Aufzählung enthält 1000 Elemente.

  • Any() würde den ersten überprüfen und dann true zurückgeben.
  • Count() würde 1000 zurückgeben, nachdem die gesamte Aufzählung durchlaufen wurde.

Dies ist möglicherweise schlimmer, wenn Sie eine der Prädikatüberschreibungen verwenden - Count () muss immer noch jedes einzelne Element überprüfen, auch wenn es nur eine Übereinstimmung gibt.

Man gewöhnt sich an die Verwendung von Any one - es macht Sinn und ist lesbar.

Eine Einschränkung: Wenn Sie eine Liste haben und nicht nur eine IEnumerable, verwenden Sie die Count-Eigenschaft dieser Liste.

Keith
quelle
Die Unterschiede zwischen Any () und Count () scheinen klar zu sein, aber der Profiling-Code von @ crucible scheint darauf hinzudeuten, dass Count () für bestimmte Implementierungen von IEnumerable <T> schneller ist. Für List <T> kann Any () kein schnelleres Ergebnis als Count () liefern, bis die Listengröße bei Tausenden von Elementen liegt. LINQ selbst muss die Count () -Methode irgendwie ernsthaft optimieren.
Matt Hamilton
3

@Konrad Was mich überrascht ist, dass ich in meinen Tests die Liste an eine akzeptierende Methode übergebe IEnumerable<T>, sodass die Laufzeit sie nicht durch Aufrufen der Count () - Erweiterungsmethode für optimieren kann IList<T>.

Ich kann nur davon ausgehen, dass die Count () - Erweiterungsmethode für IEnumerable ungefähr so ​​funktioniert:

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

... mit anderen Worten, ein bisschen Laufzeitoptimierung für den Sonderfall von IList<T>.

/ EDIT @Konrad +1 Kumpel - Sie haben Recht damit, dass es wahrscheinlicher ist, dass es eingeschaltet ist ICollection<T>.

Matt Hamilton
quelle
1

Ok, was ist mit diesem?

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

EDIT: Ich habe gerade festgestellt, dass jemand diese Lösung bereits skizziert hat. Es wurde erwähnt, dass die Any () -Methode dies tun wird, aber warum nicht selbst? Grüße

Jonny Dee
quelle
3
ABER es wird weniger prägnant, wenn Sie es richtig in einen usingBlock einschließen, da Sie sonst ein IDisposableObjekt konstruiert und es dann aufgegeben haben. Dann, natürlich, wird es mehr prägnantes , wenn Sie die Erweiterungsmethode verwenden , die bereits vorhanden ist und es nur ändern return !enumerable.Any()(was tut genau dies).
Dan Tao
Warum eine bereits vorhandene Methode umschreiben? Wie bereits erwähnt, wird Any()genau das ausgeführt, sodass das Hinzufügen genau derselben Methode mit einem anderen Namen nur verwirrend ist.
Julien N
1

Eine andere Idee:

if(enumerable.FirstOrDefault() != null)

Ich mag jedoch den Any () -Ansatz mehr.

ChulioMartinez
quelle
3
Was ist, wenn Sie eine nicht leere Liste haben, in der das erste Element null ist?
Ekevoo
1

Dies war wichtig, damit dies mit Entity Framework funktioniert:

var genericCollection = list as ICollection<T>;

if (genericCollection != null)
{
   //your code 
}
Holt Mansfield
quelle
Wie beantwortet das die Frage? Die Sammlung darf nicht null sein, wenn keine Elemente enthalten sind.
Martin Verjans
0

Wenn ich mit Count () überprüfe, führt Linq ein "SELECT COUNT (*) .." in der Datenbank aus, aber ich muss überprüfen, ob die Ergebnisse Daten enthalten, habe ich beschlossen, FirstOrDefault () anstelle von Count () einzuführen.

Vor

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

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

Nach dem

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

var itemCfop = cfop.FirstOrDefault();

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

Hier ist meine Umsetzung der Antwort von Dan Tao unter Berücksichtigung eines Prädikats:

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
quelle
-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
quelle
-3

myList.ToList().Count == 0. Das ist alles

user3149517
quelle
1
Das ist eine schreckliche Idee. ToList () sollte nicht überbeansprucht werden, da dies die vollständige Auswertung der Aufzählung erzwingt. Verwenden Sie stattdessen .Any ().
Jon Rea
-5

Diese Erweiterungsmethode funktioniert bei mir:

public static bool IsEmpty<T>(this IEnumerable<T> enumerable)
{
    try
    {
        enumerable.First();
        return false;
    }
    catch (InvalidOperationException)
    {
        return true;
    }
}
Jonny Dee
quelle
5
Vermeiden Sie solche Ausnahmen. Im obigen Code erwarten Sie eine Ausnahme für bestimmte, genau definierte Eingaben (dh leere Aufzählungen). Sie sind also keine Ausnahmen, sie sind die Regel. Dies ist ein Missbrauch dieses Kontrollmechanismus, der Auswirkungen auf die Lesbarkeit und Leistung hat. Reservieren Sie die Verwendung von Ausnahmen für wirklich außergewöhnliche Fälle.
Konrad Rudolph
Generell würde ich zustimmen. Dies ist jedoch eine Problemumgehung für eine entsprechende fehlende IsEmpty-Methode. Und ich würde argumentieren, dass eine Problemumgehung niemals der ideale Weg ist, um etwas zu tun ... Darüber hinaus ist die Absicht, insbesondere in diesem Fall, sehr klar und der "schmutzige" Code ist eingekapselt und an einem genau definierten Ort versteckt.
Jonny Dee
3
-1: Wenn Sie dies auf diese Weise tun möchten, verwenden Sie FirstOrDefault (), wie in der Antwort von ChulioMartinez.
Daniel Rose
3
Die Ausnahmebehandlung weist eine sehr schlechte Leistungseffizienz auf. Dies kann hier also die schlechteste Lösung sein.
Julien N
"Ausnahmen sollten außergewöhnlich sein." - Verwenden Sie sie nicht für den normalen Programmablauf.
Jon Rea