718 Stimmen

Welche Methode ist leistungsfähiger: .Any() vs. .Count() > 0?

Im System.Linq Namespace können wir nun unsere IEnumerable's um die beliebig() et Zählen() Erweiterungsmethoden .

Mir wurde vor kurzem gesagt, dass ich, wenn ich überprüfen möchte, ob eine Sammlung 1 oder mehr Elemente enthält, sollte ich die .Any() Erweiterungsmethode anstelle der .Count() > 0 Erweiterungsmethode, da die .Count() Erweiterungsmethode muss alle Elemente durchlaufen.

Zweitens: Einige Sammlungen haben eine Eigenschaft (keine Erweiterungsmethode), das ist Count o Length . Wäre es besser, diese zu verwenden, anstatt die .Any() o .Count() ?

ja/nein?

871voto

Marc Gravell Punkte 970173

Wenn Sie mit etwas beginnen, das eine .Length o .Count (wie zum Beispiel ICollection<T> , IList<T> , List<T> usw.) - dann ist dies die schnellste Option, da sie nicht durch die GetEnumerator() / MoveNext() / Dispose() Reihenfolge erforderlich durch Any() um zu prüfen, ob eine nicht leere IEnumerable<T> Reihenfolge.

Nur für IEnumerable<T> dann Any() 意志 allgemein schneller sein, da sie nur eine Iteration betrachten muss. Beachten Sie jedoch, dass die LINQ-to-Objects-Implementierung von Count() prüft auf ICollection<T> (mit .Count als eine Optimierung) - wenn also die zugrunde liegende Datenquelle direkt eine Liste/Sammlung, wird es keinen großen Unterschied geben. Fragen Sie mich nicht, warum es nicht die nicht-generische ICollection ...

Natürlich, wenn Sie LINQ zum Filtern usw. verwendet haben ( Where usw.), haben Sie eine auf Iterator-Blöcken basierende Sequenz, und somit diese ICollection<T> Optimierung ist nutzlos.

In der Regel mit IEnumerable<T> : bleiben Sie bei Any() ;-p

70voto

nikib3ro Punkte 19921

Nota: Ich habe diese Antwort geschrieben, als Entity Framework 4 aktuell war. Der Sinn dieser Antwort war es nicht, sich mit trivialen .Any() vs .Count() Leistungstests. Der Punkt war, zu signalisieren, dass EF weit davon entfernt ist, perfekt zu sein. Neuere Versionen sind besser... aber wenn Sie einen Teil des Codes haben, der langsam ist und EF verwendet, testen Sie mit direktem TSQL und vergleichen Sie die Leistung, anstatt sich auf Annahmen zu verlassen (die .Any() ist IMMER schneller als .Count() > 0 ).


Ich stimme mit den meisten Antworten und Kommentaren überein - insbesondere mit dem Punkt Any Signale Entwicklerabsicht besser als Count() > 0 - Ich hatte eine Situation, in der Count auf SQL Server (EntityFramework 4) um eine Größenordnung schneller ist.

Hier ist die Abfrage mit Any dass die Timeout-Ausnahme (bei ~200.000 Datensätzen):

con = db.Contacts.
    Where(a => a.CompanyId == companyId && a.ContactStatusId <= (int) Const.ContactStatusEnum.Reactivated
        && !a.NewsletterLogs.Any(b => b.NewsletterLogTypeId == (int) Const.NewsletterLogTypeEnum.Unsubscr)
    ).OrderBy(a => a.ContactId).
    Skip(position - 1).
    Take(1).FirstOrDefault();

Count Version in wenigen Millisekunden ausgeführt:

con = db.Contacts.
    Where(a => a.CompanyId == companyId && a.ContactStatusId <= (int) Const.ContactStatusEnum.Reactivated
        && a.NewsletterLogs.Count(b => b.NewsletterLogTypeId == (int) Const.NewsletterLogTypeEnum.Unsubscr) == 0
    ).OrderBy(a => a.ContactId).
    Skip(position - 1).
    Take(1).FirstOrDefault();

Ich muss einen Weg finden, um zu sehen, welche genaue SQL beide LINQs produzieren - aber es ist offensichtlich, es ist ein großer Leistungsunterschied zwischen Count et Any in einigen Fällen, und leider scheint es, dass man sich nicht einfach an die Any in allen Fällen.

EDIT: Hier sind die generierten SQLs. Wunderschön, wie Sie sehen können ;)

ANY :

exec sp\_executesql N'SELECT TOP (1) 
\[Project2\].\[ContactId\] AS \[ContactId\], 
\[Project2\].\[CompanyId\] AS \[CompanyId\], 
\[Project2\].\[ContactName\] AS \[ContactName\], 
\[Project2\].\[FullName\] AS \[FullName\], 
\[Project2\].\[ContactStatusId\] AS \[ContactStatusId\], 
\[Project2\].\[Created\] AS \[Created\]
FROM ( SELECT \[Project2\].\[ContactId\] AS \[ContactId\], \[Project2\].\[CompanyId\] AS \[CompanyId\], \[Project2\].\[ContactName\] AS \[ContactName\], \[Project2\].\[FullName\] AS \[FullName\], \[Project2\].\[ContactStatusId\] AS \[ContactStatusId\], \[Project2\].\[Created\] AS \[Created\], row\_number() OVER (ORDER BY \[Project2\].\[ContactId\] ASC) AS \[row\_number\]
    FROM ( SELECT 
        \[Extent1\].\[ContactId\] AS \[ContactId\], 
        \[Extent1\].\[CompanyId\] AS \[CompanyId\], 
        \[Extent1\].\[ContactName\] AS \[ContactName\], 
        \[Extent1\].\[FullName\] AS \[FullName\], 
        \[Extent1\].\[ContactStatusId\] AS \[ContactStatusId\], 
        \[Extent1\].\[Created\] AS \[Created\]
        FROM \[dbo\].\[Contact\] AS \[Extent1\]
        WHERE (\[Extent1\].\[CompanyId\] = @p\_\_linq\_\_0) AND (\[Extent1\].\[ContactStatusId\] <= 3) AND ( NOT EXISTS (SELECT 
            1 AS \[C1\]
            FROM \[dbo\].\[NewsletterLog\] AS \[Extent2\]
            WHERE (\[Extent1\].\[ContactId\] = \[Extent2\].\[ContactId\]) AND (6 = \[Extent2\].\[NewsletterLogTypeId\])
        ))
    )  AS \[Project2\]
)  AS \[Project2\]
WHERE \[Project2\].\[row\_number\] > 99
ORDER BY \[Project2\].\[ContactId\] ASC',N'@p\_\_linq\_\_0 int',@p\_\_linq\_\_0=4

COUNT :

exec sp\_executesql N'SELECT TOP (1) 
\[Project2\].\[ContactId\] AS \[ContactId\], 
\[Project2\].\[CompanyId\] AS \[CompanyId\], 
\[Project2\].\[ContactName\] AS \[ContactName\], 
\[Project2\].\[FullName\] AS \[FullName\], 
\[Project2\].\[ContactStatusId\] AS \[ContactStatusId\], 
\[Project2\].\[Created\] AS \[Created\]
FROM ( SELECT \[Project2\].\[ContactId\] AS \[ContactId\], \[Project2\].\[CompanyId\] AS \[CompanyId\], \[Project2\].\[ContactName\] AS \[ContactName\], \[Project2\].\[FullName\] AS \[FullName\], \[Project2\].\[ContactStatusId\] AS \[ContactStatusId\], \[Project2\].\[Created\] AS \[Created\], row\_number() OVER (ORDER BY \[Project2\].\[ContactId\] ASC) AS \[row\_number\]
    FROM ( SELECT 
        \[Project1\].\[ContactId\] AS \[ContactId\], 
        \[Project1\].\[CompanyId\] AS \[CompanyId\], 
        \[Project1\].\[ContactName\] AS \[ContactName\], 
        \[Project1\].\[FullName\] AS \[FullName\], 
        \[Project1\].\[ContactStatusId\] AS \[ContactStatusId\], 
        \[Project1\].\[Created\] AS \[Created\]
        FROM ( SELECT 
            \[Extent1\].\[ContactId\] AS \[ContactId\], 
            \[Extent1\].\[CompanyId\] AS \[CompanyId\], 
            \[Extent1\].\[ContactName\] AS \[ContactName\], 
            \[Extent1\].\[FullName\] AS \[FullName\], 
            \[Extent1\].\[ContactStatusId\] AS \[ContactStatusId\], 
            \[Extent1\].\[Created\] AS \[Created\], 
            (SELECT 
                COUNT(1) AS \[A1\]
                FROM \[dbo\].\[NewsletterLog\] AS \[Extent2\]
                WHERE (\[Extent1\].\[ContactId\] = \[Extent2\].\[ContactId\]) AND (6 = \[Extent2\].\[NewsletterLogTypeId\])) AS \[C1\]
            FROM \[dbo\].\[Contact\] AS \[Extent1\]
        )  AS \[Project1\]
        WHERE (\[Project1\].\[CompanyId\] = @p\_\_linq\_\_0) AND (\[Project1\].\[ContactStatusId\] <= 3) AND (0 = \[Project1\].\[C1\])
    )  AS \[Project2\]
)  AS \[Project2\]
WHERE \[Project2\].\[row\_number\] > 99
ORDER BY \[Project2\].\[ContactId\] ASC',N'@p\_\_linq\_\_0 int',@p\_\_linq\_\_0=4

Es scheint, dass reine Where mit EXISTS viel schlechter funktioniert als die Berechnung von Count und die anschließende Durchführung von Where mit Count == 0.

Lassen Sie mich wissen, wenn Sie Jungs sehen einige Fehler in meine Erkenntnisse. Was kann aus all dies unabhängig von Any vs Count Diskussion genommen werden ist, dass jede komplexere LINQ ist viel besser, wenn als Stored Procedure umgeschrieben;).

70voto

gregmac Punkte 23263

Die genauen Details unterscheiden sich bei .NET Framework und .NET Core ein wenig, aber es hängt auch ein wenig davon ab, was Sie tun: Wenn Sie eine ICollection o ICollection<T> Typ (z. B. mit List<T> ) gibt es eine .Count Eigenschaft, auf die man leicht zugreifen kann, während für andere Typen eine Aufzählung erforderlich sein kann.

TL;DR:

Verwenden Sie .Count > 0 wenn die Eigenschaft vorhanden ist, andernfalls .Any() .

使用方法 .Count() > 0 es niemals die beste Option, und in einigen Fällen könnte sie sogar deutlich langsamer sein.

Dies gilt sowohl für .NET Framework als auch für .NET Core.


Jetzt können wir in die Details eintauchen.

Listen und Sammlungen

Beginnen wir mit einem sehr häufigen Fall: der Verwendung von List<T> (was auch eine ICollection<T> ).

El .Count Eigenschaft ist implementiert als:

    private int _size;

    public int Count {
        get {
            Contract.Ensures(Contract.Result<int>() >= 0);
            return _size; 
        }
    }

Dies bedeutet, dass _size wird unterhalten von Add() , Remove() usw., und da es sich nur um den Zugriff auf ein Feld handelt, ist dies eine extrem billige Operation - wir müssen nicht über Werte iterieren.

ICollection et ICollection<T> beide haben .Count et Die meisten Typen, die sie umsetzen, werden dies wahrscheinlich auf ähnliche Weise tun.

Andere IEnumerables

Jede andere IEnumerable Typen, die nicht auch ICollection erfordern den Beginn einer Aufzählung, um festzustellen, ob sie leer sind oder nicht. Der Schlüsselfaktor für die Leistung ist, ob wir am Ende ein einzelnes Element (ideal) oder die gesamte Sammlung (relativ teuer) aufzählen.

Wenn die Auflistung tatsächlich E/A verursacht, z. B. durch das Lesen aus einer Datenbank oder von einer Festplatte, kann dies zu einem großen Leistungsverlust führen.


.NET-Rahmenwerk .Any()

In .NET Framework (4.8) ist die Any() Umsetzung ist:

public static bool Any<TSource>(this IEnumerable<TSource> source) {
    if (source == null) throw Error.ArgumentNull("source");
    using (IEnumerator<TSource> e = source.GetEnumerator()) {
        if (e.MoveNext()) return true;
    }
    return false;
}

Das heißt, egal was passiert, es wird ein neues Enumerator-Objekt geholt und einmal versucht, es zu durchlaufen. Das ist teurer als der Aufruf der List<T>.Count Eigenschaft, aber zumindest wird nicht die gesamte Liste durchlaufen.

.NET-Rahmenwerk .Count()

In .NET Framework (4.8) ist die Count() Umsetzung ist (grundsätzlich):

public static int Count<TSource>(this IEnumerable<TSource> source)
{
    ICollection<TSource> collection = source as ICollection<TSource>;
    if (collection != null)
    { 
        return collection.Count;
    }
    int num = 0;
    using (IEnumerator<TSource> enumerator = source.GetEnumerator())
    {
        while (enumerator.MoveNext())
        {
            num = checked(num + 1);
        }
        return num;
    }
}

Falls vorhanden, ICollection.Count verwendet wird, aber ansonsten wird die Sammlung aufgezählt.


.NET Core .Any()

Der LINQ Any() Implementierung in .NET Core ist viel intelligenter. Sie können die vollständige Quelle hier sondern die für diese Diskussion relevanten Teile:

    public static bool Any<TSource>(this IEnumerable<TSource> source)
    {
        //..snip..

        if (source is ICollection<TSource> collectionoft)
        {
            return collectionoft.Count != 0;
        }

        //..snip..

        using (IEnumerator<TSource> e = source.GetEnumerator())
        {
            return e.MoveNext();
        }
    }

Weil ein List<T> es un ICollection<T> auf, so wird die Count Eigenschaft (und obwohl es eine andere Methode aufruft, gibt es keine zusätzlichen Zuweisungen).

.NET Core .Count()

Die .NET Core-Implementierung ( Quelle ) ist im Grunde dasselbe wie .NET Framework (siehe oben), und daher wird es ICollection.Count falls vorhanden, und listet ansonsten die Sammlung auf.


Zusammenfassung

.NET-Rahmenwerk

  • Con ICollection :

    • .Count > 0 ist am besten
    • .Count() > 0 ist in Ordnung, ruft aber letztlich nur ICollection.Count
    • .Any() wird langsamer sein, da es ein einzelnes Element aufzählt
  • Mit nicht ICollection (nein .Count Eigenschaft)

    • .Any() ist am besten geeignet, da es nur ein einziges Element aufzählt
    • .Count() > 0 ist schlecht, da es zu einer vollständigen Aufzählung führt

.NET Core

  • .Count > 0 ist am besten, wenn verfügbar ( ICollection )
  • .Any() ist in Ordnung, und wird entweder ICollection.Count > 0 oder ein einzelnes Element aufzählen
  • .Count() > 0 ist schlecht, da sie eine vollständige Aufzählung verursacht

36voto

kamil-mrzyglod Punkte 4798

Da dies ein sehr beliebtes Thema ist und die Antworten unterschiedlich ausfallen, musste ich das Problem neu betrachten.

Testumgebung: EF 6.1.3, SQL Server, 300k Datensätze

Tischmodell :

class TestTable
{
    [Key]
    public int Id { get; set; }

    public string Name { get; set; }

    public string Surname { get; set; }
}

Test-Code:

class Program
{
    static void Main()
    {
        using (var context = new TestContext())
        {
            context.Database.Log = Console.WriteLine;

            context.TestTables.Where(x => x.Surname.Contains("Surname")).Any(x => x.Id > 1000);
            context.TestTables.Where(x => x.Surname.Contains("Surname") && x.Name.Contains("Name")).Any(x => x.Id > 1000);
            context.TestTables.Where(x => x.Surname.Contains("Surname")).Count(x => x.Id > 1000);
            context.TestTables.Where(x => x.Surname.Contains("Surname") && x.Name.Contains("Name")).Count(x => x.Id > 1000);

            Console.ReadLine();
        }
    }
}

Ergebnisse:

Any() ~ 3ms

Count() ~ 230ms für die erste Abfrage, ~ 400ms für die zweite

Bemerkung:

In meinem Fall hat EF kein SQL generiert, wie @Ben in seinem Beitrag erwähnt.

13voto

Ben Punkte 2352

EDIT : es wurde in EF Version 6.1.1. behoben und diese Antwort ist nicht mehr aktuell

Bei SQL Server und EF4-6 ist Count() etwa zwei Mal schneller als Any().

Wenn Sie Table.Any() ausführen, wird etwas erzeugt wie( Achtung: Verletzen Sie nicht Ihr Gehirn, wenn Sie versuchen, es zu verstehen )

SELECT 
CASE WHEN ( EXISTS (SELECT 
    1 AS [C1]
    FROM [Table] AS [Extent1]
)) THEN cast(1 as bit) WHEN ( NOT EXISTS (SELECT 
    1 AS [C1]
    FROM [Table] AS [Extent2]
)) THEN cast(0 as bit) END AS [C1]
FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]

die 2 Scans von Zeilen mit Ihrer Bedingung erfordert.

Ich schreibe nicht gerne Count() > 0 weil es meine Absicht verbirgt. Ich ziehe es vor, dafür ein benutzerdefiniertes Prädikat zu verwenden:

public static class QueryExtensions
{
    public static bool Exists<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
    {
        return source.Count(predicate) > 0;
    }
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X