Która metoda działa lepiej: .Any () vs .Count ()> 0?

576

w System.Linqprzestrzeni nazw, możemy rozszerzyć nasze IEnumerable jest mieć Obojętnie () i count () metody rozszerzenie .

Ostatnio powiedziano mi, że jeśli chcę sprawdzić, czy kolekcja zawiera 1 lub więcej elementów, powinienem użyć .Any()metody rozszerzenia zamiast .Count() > 0metody rozszerzenia, ponieważ .Count()metoda rozszerzenia musi iterować wszystkie elementy.

Po drugie, niektóre kolekcje mają właściwość (nie metodę rozszerzenia), którą jest Countlub Length. Czy lepiej byłoby użyć ich zamiast .Any()lub .Count()?

tak / nie?

Pure.Krome
źródło
Lepiej używać Any () na Wyliczeniach i liczyć na Kolekcje. Jeśli ktoś uważa, że ​​pisanie „(somecollection.Count> 0)” pomyli lub spowoduje problemy z czytelnością, lepiej napisz jako metodę rozszerzenia o nazwie Any (). Wtedy wszyscy byli zadowoleni. Zarówno pod względem wydajności, jak i czytelności. Aby cały Twój kod był spójny, a indywidualny programista w projekcie nie musiał się martwić o wybranie Count vs. Any.
Mahesh Bongani,

Odpowiedzi:

708

Jeśli zaczynasz z czymś, co ma .Lengthlub .Count(takich jak ICollection<T>, IList<T>, List<T>itp) - wtedy będzie to najszybsza opcja, ponieważ nie muszą przejść przez GetEnumerator()/ MoveNext()/ Dispose()sekwencji wymaganych Any()do sprawdzenia niepusty IEnumerable<T>sekwencji .

Za jedyne IEnumerable<T>, to Any()będzie na ogół szybciej, ponieważ ma tylko spojrzeć na jednej iteracji. Należy jednak pamiętać, że implementacja LINQ-Objects Count()sprawdza ICollection<T>(używa .Countjako optymalizację) - więc jeśli źródłowym źródłem danych jest bezpośrednio lista / kolekcja, nie będzie dużej różnicy. Nie pytaj mnie, dlaczego nie korzysta z niejakiego rodzaju ICollection...

Oczywiście, jeśli użyłeś LINQ do filtrowania itp. Where, Będziesz miał sekwencję opartą na bloku iteratora, więc ta ICollection<T>optymalizacja jest bezużyteczna.

Ogólnie z IEnumerable<T>: trzymaj się Any();-p

Marc Gravell
źródło
9
Marc: ICollection <T> tak naprawdę nie pochodzi od ICollection. Byłem również zaskoczony, ale Odbłyśnik nie kłamie.
Bryan Watts
7
Czy implementacja Any () nie sprawdza interfejsu ICollection i nie sprawdza właściwości Count?
derigel
313
Myślę, że istnieje inny powód używania Any () przez większość czasu. Sygnalizuje dokładne zamiary dewelopera. Jeśli nie jesteś zainteresowany znajomością liczby elementów, ale tylko jeśli są jakieś, to somecollection.Any () jest prostsze i bardziej przejrzyste niż somecollection.Count> 0
TJKjaer
13
@huttelihut - Ilu znasz programistów, którzy są naprawdę zdezorientowani tym stwierdzeniem (somecollection.Count > 0)? Czy cały nasz kod przed wprowadzeniem metody LINQ .Any () był trudny do zrozumienia?
CraigTP,
25
@JLRishe - Nadal uważam, że someCollection.Count > 0jest to tak samo jasne, jak someCollection.Any()i ma dodatkową zaletę polegającą na większej wydajności i niewymaganiu LINQ. To prawda, że ​​jest to bardzo prosty przypadek, a inne konstrukcje korzystające z operatorów LINQ przekazują intencje programistów znacznie jaśniej niż równoważna opcja inna niż LINQ.
CraigTP
65

Uwaga: napisałem tę odpowiedź, gdy Entity Framework 4 był aktualny. Celem tej odpowiedzi nie było przejście do testów trywialnych .Any()vs .Count()wydajnościowych. Chodziło o to, aby zasygnalizować, że EF jest daleki od ideału. Nowsze wersje są lepsze ... ale jeśli masz część kodu, który jest wolny i używa EF, przetestuj z bezpośrednim TSQL i porównaj wydajność zamiast polegać na założeniach ( .Any()to ZAWSZE jest szybsze niż .Count() > 0).


Chociaż zgadzam się z większością głosowanych odpowiedzi i komentarzy - szczególnie w kwestii Any, w której deweloper sygnalizuje zamiary lepsze niż Count() > 0- miałem sytuację, w której Count jest szybszy o rząd wielkości na SQL Server (EntityFramework 4).

Oto zapytanie z Anytym wyjątkiem limitu czasu (w ~ 200 000 rekordów):

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 wersja wykonywana w ciągu milisekund:

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

Muszę znaleźć sposób, aby zobaczyć, jaki dokładnie SQL produkują oba LINQ - ale jest oczywiste, że istnieje ogromna różnica w wydajności między, Counta Anyw niektórych przypadkach, i niestety wydaje się, że nie możesz po prostu trzymać się Anywe wszystkich przypadkach.

EDYCJA: Tutaj są generowane zapytania SQL. Piękności jak widać;)

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], 
[Projekt2]. [Utworzono] AS [Utworzono]
OD (WYBIERZ [Projekt2]. [ContactId] AS [ContactId], [Project2]. [CompanyId] AS [CompanyId], [Project2]. [ContactName] AS [ContactName], [Project2]. [FullName] AS [FullName] , [Project2]. [ContactStatusId] AS [ContactStatusId], [Project2]. [Utworzono] AS [Utworzono], row_number () OVER (ORDER BY [Project2]. [ContactId] ASC) AS [row_number]
    OD (WYBIERZ 
        [Extent1]. [ContactId] AS [ContactId], 
        [Extent1]. [CompanyId] AS [CompanyId], 
        [Extent1]. [ContactName] AS [ContactName], 
        [Extent1]. [FullName] AS [FullName], 
        [Extent1]. [ContactStatusId] AS [ContactStatusId], 
        [Extent1]. [Utworzono] AS [Utworzono]
        FROM [dbo]. [Kontakt] AS [Extent1]
        GDZIE ([Extent1]. [CompanyId] = @ p__linq__0) ORAZ ([Extent1]. [ContactStatusId] <= 3) ORAZ (NIE ISTNIEJE (WYBIERZ 
            1 AS [C1]
            FROM [dbo]. [NewsletterLog] AS [Extent2]
            GDZIE ([Extent1]. [ContactId] = [Extent2]. [ContactId]) ORAZ (6 = [Extent2]. [NewsletterLogTypeId])
        ))
    ) AS [Project2]
) AS [Project2]
GDZIE [Projekt2]. [Numer_wiersza]> 99
ZAMÓW PRZEZ [Projekt2]. [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], 
[Projekt2]. [Utworzono] AS [Utworzono]
OD (WYBIERZ [Projekt2]. [ContactId] AS [ContactId], [Project2]. [CompanyId] AS [CompanyId], [Project2]. [ContactName] AS [ContactName], [Project2]. [FullName] AS [FullName] , [Project2]. [ContactStatusId] AS [ContactStatusId], [Project2]. [Utworzono] AS [Utworzono], row_number () OVER (ORDER BY [Project2]. [ContactId] ASC) AS [row_number]
    OD (WYBIERZ 
        [Projekt1]. [ContactId] AS [ContactId], 
        [Projekt1]. [CompanyId] AS [CompanyId], 
        [Project1]. [ContactName] AS [ContactName], 
        [Projekt1]. [Pełna nazwa] AS [Pełna nazwa], 
        [Projekt1]. [ContactStatusId] AS [ContactStatusId], 
        [Projekt1]. [Utworzono] AS [Utworzono]
        OD (WYBIERZ 
            [Extent1]. [ContactId] AS [ContactId], 
            [Extent1]. [CompanyId] AS [CompanyId], 
            [Extent1]. [ContactName] AS [ContactName], 
            [Extent1]. [FullName] AS [FullName], 
            [Extent1]. [ContactStatusId] AS [ContactStatusId], 
            [Extent1]. [Utworzono] AS [Utworzono], 
            (WYBIERZ 
                COUNT (1) AS [A1]
                FROM [dbo]. [NewsletterLog] AS [Extent2]
                GDZIE ([Extent1]. [ContactId] = [Extent2]. [ContactId]) ORAZ (6 = [Extent2]. [NewsletterLogTypeId])) AS [C1]
            FROM [dbo]. [Kontakt] AS [Extent1]
        ) AS [Project1]
        GDZIE ([Projekt1]. [CompanyId] = @ p__linq__0) ORAZ ([Projekt1]. [ContactStatusId] <= 3) ORAZ (0 = [Projekt1]. [C1])
    ) AS [Project2]
) AS [Project2]
GDZIE [Projekt2]. [Numer_wiersza]> 99
ZAMÓW PRZEZ [Projekt2]. [ContactId] ASC ', N' @ p__linq__0 int ', @ p__linq__0 = 4

Wydaje się, że czysty Where with EXISTS działa znacznie gorzej niż obliczanie Count, a następnie wykonywanie Where with Count == 0.

Daj mi znać, jeśli zauważysz jakiś błąd w moich ustaleniach. Bez względu na dyskusję Any vs. Count można wyciągnąć z tego wszystko, że bardziej skomplikowane LINQ jest o wiele lepiej, gdy zostanie przepisane jako procedura przechowywana;).

nikib3ro
źródło
2
Chciałbym zobaczyć niektóre plany zapytań Sql, które są generowane przez każde zapytanie linq dla każdego scenariusza.
Pure.Krome
43
oparte na SQL, wszystko co mogę powiedzieć to: oba zapytania wyglądają okropnie. Wiedziałem, że istnieje powód, dla którego zwykle piszę własny TSQL ...
Marc Gravell
! Każdy musiałby przeglądać wszystkie rzędy tak samo jak Count. To, że twój przykład daje tak przerażający wynik, jest w najgorszym przypadku trochę dziwne! Każdy powinien być tylko trochę wolniejszy niż Count. W twoim przypadku szukałbym sposobów uproszczenia wyboru, być może dzieląc go na etapy lub zmieniając warunki, jeśli to możliwe. Ale twój punkt, w którym zasada Any jest lepsza niż Count nie ma zastosowania! Każdy lepszy niż Count jest bardzo dobry.
Wygięty
25

Ponieważ jest to dość popularny temat i odpowiedzi różnią się, musiałem przyjrzeć się temu problemowi od nowa.

Testowanie środowiska: EF 6.1.3, SQL Server, 300 000 rekordów

Model stołu :

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

    public string Name { get; set; }

    public string Surname { get; set; }
}

Kod testowy:

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

Wyniki:

Any () ~ 3ms

Count () ~ 230ms dla pierwszego zapytania, ~ 400ms dla drugiego

Uwagi:

W moim przypadku EF nie generował kodu SQL takiego jak @Ben wspomnianego w jego poście.

kamil-mrzyglod
źródło
4
Aby uzyskać prawidłowe porównanie, powinieneś to zrobić Count() > 0. : D
Andrew
1
Andrew, Count ()> 0 nie będzie działał inaczej niż Count () w tym konkretnym teście.
CodeMonkeyForHire
11

EDYCJA: zostało to naprawione w wersji EF 6.1.1. i ta odpowiedź nie jest już aktualna

W przypadku SQL Server i EF4-6 funkcja Count () działa około dwa razy szybciej niż Any ().

Po uruchomieniu Table.Any () wygeneruje coś w rodzaju ( alert: nie rań mózgu próbując to zrozumieć )

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]

który wymaga 2 skanów wierszy z twoim stanem.

Nie lubię pisać, Count() > 0bo ukrywa moją intencję. Wolę użyć do tego niestandardowego predykatu:

public static class QueryExtensions
{
    public static bool Exists<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
    {
        return source.Count(predicate) > 0;
    }
}
Ben
źródło
Też to zauważyłem. SQL Any () nie ma żadnego sensu. Nie jestem pewien, dlaczego tego nie robią: PRZYPADEK KIEDY (ISTNIEJE (sql)) NASTĘPNIE 1 KONIEC 0. Nie mogę wymyślić powodu, dla którego muszą zrobić NIE
ISTNIEJE
To nieprawda. Przypadkowo znalazłeś zły plan zapytań. To się stało. Każdy jest prawie zawsze szybszy.
usr
Sprawdziłem sql wygenerowany w 6.1.3, naprawiono go: WYBIERZ PRZYPADEK, GDZIE (ISTNIEJE (WYBIERZ 1 JAK [C1] OD [dbo]. [TestTables] JAK [Extent1] GDZIE [Extent1]. [Id]> 1000)) NASTĘPNIE rzut (1 jako bit) ELSE rzut (0 jako bit) KONIEC JAK [C1] OD (WYBIERZ 1 JAK X) AS [SingleRowTable1]
Ben
6

To zależy, jak duży jest zestaw danych i jakie są wymagania dotyczące wydajności?

Jeśli to nic gigantycznego, użyj najbardziej czytelnej formy, która dla mnie jest dowolna, ponieważ jest krótsza i czytelniejsza niż równanie.

Timothy Gonzalez
źródło
2

O Count () metody, jeśli IEnumarable jest ICollection , wtedy nie możemy iterate wszystkich elementów, ponieważ możemy odzyskać hrabiego pole kolekcji ICollection , jeśli IEnumerable nie jest ICollection musimy iterate wszystkich elementów przy użyciu jednocześnie z MoveNext , spójrz .NET Framework Code:

public static int Count<TSource>(this IEnumerable<TSource> source)
{
    if (source == null) 
        throw Error.ArgumentNull("source");

    ICollection<TSource> collectionoft = source as ICollection<TSource>;
    if (collectionoft != null) 
        return collectionoft.Count;

    ICollection collection = source as ICollection;
    if (collection != null) 
        return collection.Count;

    int count = 0;
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
        checked
        {
            while (e.MoveNext()) count++;
        }
    }
    return count;
}

Referencje: Źródło referencyjne Wyliczalne

Thiago Coelho
źródło
2

Możesz zrobić prosty test, aby to rozgryźć:

var query = //make any query here
var timeCount = new Stopwatch();
timeCount.Start();
if (query.Count > 0)
{
}
timeCount.Stop();
var testCount = timeCount.Elapsed;

var timeAny = new Stopwatch();
timeAny.Start();
if (query.Any())
{
}
timeAny.Stop();
var testAny = timeAny.Elapsed;

Sprawdź wartości testCount i testAny.

Bronks
źródło
1
Oto test z twoim kodem dla własności Count vs. Any () Count wygrywa właściwość vs Any () z + 2x - link
Stanislav Prusac 15.04.18
1
Aby uzyskać lepszy wynik, możesz wykonać te porównania 1000 razy (lub więcej). Pomaga uśrednić wyniki i uniknąć przypadkowych skoków.
Rzymski
Podczas testowania, jak wyżej wspomniana metoda, należy wziąć pod uwagę wiele innych czynników, takich jak obciążenie bazy danych / sieci, planowanie buforowania po stronie bazy danych itp. Aby wykonać dokładny test, należy również zaprojektować izolowane i dokładne środowisko
Vahid Farahmandian,
dla lepszego porównania należy Countzastąpić metodę Count () vs .Any () nie właściwość. Potrzebujesz czasu na iteracje.
daremachine
0

Jeśli używasz Entity Framework i masz ogromną tabelę z wieloma rekordami, Any () będzie znacznie szybsze. Pamiętam, jak kiedyś chciałem sprawdzić, czy stół jest pusty i ma miliony wierszy. Zakończenie Count ()> 0 zajęło 20-30 sekund. To było natychmiastowe dzięki Any () .

Any () może zwiększać wydajność, ponieważ może nie być konieczne iterowanie kolekcji w celu uzyskania liczby rzeczy. Musi trafić tylko jeden z nich. Lub, powiedzmy, LINQ-to-Entities, wygenerowany SQL będzie JEŚLI ISTNIEJE (...), a nie WYBIERZ LICZNIK ... lub nawet WYBIERZ * ....

Janmejay Kumar
źródło