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

W przestrzeni nazw System.Linq możemy teraz rozszerzyć nasze IEnumerable o Any() i Count() metody rozszerzenia .

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

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

Yea / nae ?

Author: Pure.Krome, 2008-11-20

8 answers

Jeśli zaczynasz od czegoś, co ma .Length LUB .Count (np. ICollection<T>, IList<T>, List<T>, etc) - wtedy będzie to najszybsza opcja, ponieważ nie musi przejść przez GetEnumerator()/MoveNext()/Dispose() Sekwencja wymagana przez Any() do sprawdzenia niepustej sekwencji IEnumerable<T>.

Dla tylko IEnumerable<T>, wtedy Any() będzie ogólnie będzie szybciej, ponieważ wystarczy spojrzeć na jedną iterację. Należy jednak pamiętać, że implementacja LINQ-to-Objects Count() sprawdza ICollection<T> (używając .Count jako optymalizacja) - więc jeśli źródłem danych jest bezpośrednio lista/kolekcja, nie będzie dużej różnicy. Nie pytaj mnie, dlaczego nie używa Nie-generycznego ICollection...

Oczywiście, jeśli użyłeś LINQ do filtrowania itp. (Where itd.), będziesz miał sekwencję opartą na blokach iteracyjnych, więc ta optymalizacja ICollection<T> jest bezużyteczna.

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

 609
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-11-20 12:51:19

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


Choć zgadzam się z większością głosowanych odpowiedzi i komentarze-szczególnie w punkcie Any sygnały intencja programisty lepsze niż Count() > 0 - miałem sytuację, w której Licznik jest szybszy o rząd wielkości na serwerze SQL (EntityFramework 4).

Oto zapytanie z Any że wyjątek timeout Thew (na ~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ć, co dokładnie SQL oba LINQs produkować - ale to oczywiste, że jest ogromna wydajność różnica między Count i Any w niektórych przypadkach, i niestety wydaje się, że nie można po prostu trzymać się Any we wszystkich przypadkach.

EDIT: tutaj są generowane 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], 
[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

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

Dajcie znać, jeśli zobaczycie jakiś błąd w moich ustaleniach. Co można z tego wszystkiego wyciągnąć niezależnie od Count dyskusja jest taka, że każdy bardziej złożony LINQ jest o wiele lepszy po przepisaniu jako procedura składowana ;).
 55
Author: kape123,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-01-30 18:16:39

Ponieważ jest to dość popularny temat i odpowiedzi różnią się, musiałem spojrzeć na nowy problem.

Testowanie env: EF 6.1.3, SQL Server, 300K records

Model Tabeli :

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

    public string Name { get; set; }

    public string Surname { get; set; }
}

Kod badania:

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ł SQL, tak jak @Ben wspomniał w swoim poście.

 16
Author: kamil-mrzyglod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-28 08:14:57

EDIT: został poprawiony w wersji EF 6.1.1. a ta odpowiedź nie jest już aktualna

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

Kiedy uruchomisz tabelę.Any(), to wygeneruje coś w rodzaju (alert: nie krzywdź 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]

To wymaga 2 skanów wierszy z twoim stanem.

Nie lubię pisać Count() > 0 ponieważ ukrywa to moją intencję. Wolę używać niestandardowego predykatu dla to:

public static class QueryExtensions
{
    public static bool Exists<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
    {
        return source.Count(predicate) > 0;
    }
}
 10
Author: Ben,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-13 21:58:50

Cóż, metoda rozszerzenia .Count() nie użyje właściwości .Count, ale zakładam, że nie użyjesz metody .Count() dla prostej kolekcji, ale raczej na końcu instrukcji LINQ z kryteriami filtrowania itp.

W tym kontekście, .Any() będzie szybszy niż .Count() > 0.

 7
Author: Lasse Vågsæther Karlsen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-09 13:37:46

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

Jeśli to nic wielkiego użyj najbardziej czytelnej formy, który dla mnie jest dowolny, ponieważ jest krótszy i czytelny, a nie równanie.

 5
Author: Timothy Gonzalez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-22 23:43:13

Możesz zrobić prosty test, aby to zrozumieć:

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.

 2
Author: Bronks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-08 15:18:41

O metodzie Count(), Jeśli IEnumerable jest ICollection, to nie możemy iterować we wszystkich elementach, ponieważ możemy pobrać Count pole z ICollection, Jeśli IEnumerable nie jest ICollection musimy iterować wszystkie elementy używając while z MoveNext, spójrz na kod. NET Framework:

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

Reference: Reference Source Enumerable

 1
Author: Thiago Coelho,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-28 17:35:36