Quale metodo funziona meglio: .Any () vs .Count ()> 0?


578

nello System.Linqspazio dei nomi, ora possiamo estendere i nostri IEnumerable per avere i metodi di estensione Any () e Count () .

Mi è stato detto di recente che se voglio verificare che una raccolta contenga 1 o più elementi al suo interno, dovrei usare il .Any()metodo di estensione anziché il .Count() > 0metodo di estensione perché il .Count()metodo di estensione deve scorrere tutti gli elementi.

In secondo luogo, alcune raccolte hanno una proprietà (non un metodo di estensione) che è Counto Length. Sarebbe meglio usare quelli, invece di .Any()o .Count()?

sì / nae?


Meglio usare Any () su Enumerables e Count on Collections. Se qualcuno sente di scrivere "(somecollection.Count> 0)" confonderà o causerà problemi di leggibilità, è meglio scriverlo come metodo di estensione nominandolo Any (). Quindi tutti soddisfatti. Dal punto di vista delle prestazioni e della leggibilità. In modo che tutto il codice abbia coerenza e che i singoli sviluppatori nel progetto non debbano preoccuparsi di scegliere Count vs Any.
— Mahesh Bongani,

Risposte:


709

Se si inizia con qualcosa che ha una .Lengtho .Count(come ICollection<T>, IList<T>, List<T>, ecc) - allora questo sarà l'opzione più veloce, dal momento che non ha bisogno di passare attraverso il GetEnumerator()/ MoveNext()/ Dispose()sequenza richiesta per Any()per verificare la presenza di un non-vuotoIEnumerable<T> sequenza .

In questo caso IEnumerable<T>, Any()in genere sarà più veloce, poiché deve solo guardare una iterazione. Tuttavia, tieni presente che l'implementazione di LINQ-to-Objects Count()verifica ICollection<T>(usando .Countcome ottimizzazione) - quindi se la tua sorgente di dati sottostante è direttamente un elenco / raccolta, non ci sarà una grande differenza. Non chiedermi perché non utilizza il non genericoICollection ...

Naturalmente, se hai utilizzato LINQ per filtrarlo, ecc ( Whereecc.), Avrai una sequenza basata su blocchi di iteratori, e quindi questoICollection<T> ottimizzazione è inutile.

In generale con IEnumerable<T>: bastone con Any();-p


9
Marc: ICollection <T> in realtà non deriva da ICollection. Sono stato anche sorpreso, ma Reflector non mente.
— Bryan Watts,

7
L'implementazione () non controlla l'interfaccia ICollection e controlla la proprietà Count?
— derigel,

313
Penso che ci sia un altro motivo per usare Any () il più delle volte. Segnala l'intento preciso dello sviluppatore. Se non sei interessato a conoscere il numero di elementi, ma solo se ce ne sono alcuni, allora somecollection.Any () è più semplice e più chiaro di somecollection.Count> 0
— TJKjaer

13
@huttelihut - Quanti sviluppatori conosci che sono sinceramente confusi dall'affermazione (somecollection.Count > 0)? Tutto il nostro codice era prima dell'introduzione del metodo .Any () di LINQ difficile da capire?
— CraigTP,

25
@JLRishe - Continuo a ritenere che someCollection.Count > 0sia altrettanto chiaro someCollection.Any()e che abbia l'ulteriore vantaggio di maggiori prestazioni e di non richiedere LINQ. Certo, questo è un caso molto semplice e altri costrutti che utilizzano operatori LINQ daranno agli sviluppatori intenti molto più chiari dell'equivalente opzione non LINQ.
— CraigTP,

65

Nota: ho scritto questa risposta quando Entity Framework 4 era effettivo. Il punto di questa risposta non era quello di entrare in banali test di prestazioni .Any()vs. .Count()Il punto era segnalare che EF è tutt'altro che perfetto. Le versioni più recenti sono migliori ... ma se hai una parte di codice che è lenta e utilizza EF, prova con TSQL diretto e confronta le prestazioni piuttosto che fare affidamento su ipotesi (che .Any()è SEMPRE più veloce di .Count() > 0).


Mentre sono d'accordo con la maggior parte delle risposte e dei commenti più votati, in particolare sui Anysegnali di punto l'intento dello sviluppatore è migliore diCount() > 0 , ho avuto una situazione in cui Count è più veloce per ordine di grandezza su SQL Server (EntityFramework 4).

Ecco una query con Anyquell'eccezione di timeout thew (su ~ 200.000 record):

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 versione eseguita in millisecondi:

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

Ho bisogno di trovare un modo per vedere quale SQL esatto producono entrambi i LINQ, ma è ovvio che c'è un'enorme differenza di prestazioni tra Counte Anyin alcuni casi, e sfortunatamente sembra che non si possa semplicemente attenersi Anyin tutti i casi.

EDIT: qui vengono generati gli SQL. Bellezze come puoi vedere;)

ANY:

exec sp_executesql N'SELECT TOP (1) 
[Project2]. [ContactId] AS [ContactId], 
[Project2]. [CompanyId] AS [CompanyId], 
[Project2]. [ContactName] AS [ContactName], 
[Progetto2]. [Nome completo] AS [Nome completo], 
[Project2]. [ContactStatusId] AS [ContactStatusId], 
[Progetto2]. [Creato] AS [Creato]
FROM (SELEZIONA [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]
    DA (SELEZIONA 
        [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]. [Contatti] AS [Extent1]
        DOVE ([Extent1]. [CompanyId] = @ p__linq__0) AND ([Extent1]. [ContactStatusId] <= 3) E (NON ESISTE (SELEZIONA 
            1 AS [C1]
            FROM [dbo]. [NewsletterLog] AS [Extent2]
            WHERE ([Extent1]. [ContactId] = [Extent2]. [ContactId]) AND (6 = [Extent2]. [NewsletterLogTypeId])
        ))
    ) AS [Project2]
) AS [Project2]
DOVE [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], 
[Progetto2]. [Nome completo] AS [Nome completo], 
[Project2]. [ContactStatusId] AS [ContactStatusId], 
[Progetto2]. [Creato] AS [Creato]
FROM (SELEZIONA [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]
    DA (SELEZIONA 
        [Progetto1]. [ContactId] AS [ContactId], 
        [Project1]. [CompanyId] AS [CompanyId], 
        [Project1]. [ContactName] AS [ContactName], 
        [Progetto1]. [Nome completo] AS [Nome completo], 
        [Project1]. [ContactStatusId] AS [ContactStatusId], 
        [Progetto1]. [Creato] AS [Creato]
        DA (SELEZIONA 
            [Extent1]. [ContactId] AS [ContactId], 
            [Extent1]. [CompanyId] AS [CompanyId], 
            [Extent1]. [ContactName] AS [ContactName], 
            [Extent1]. [FullName] AS [FullName], 
            [Extent1]. [ContactStatusId] AS [ContactStatusId], 
            [Extent1]. [Created] AS [Created], 
            (SELEZIONARE 
                COUNT (1) AS [A1]
                FROM [dbo]. [NewsletterLog] AS [Extent2]
                DOVE ([Extent1]. [ContactId] = [Extent2]. [ContactId]) AND (6 = [Extent2]. [NewsletterLogTypeId])) AS [C1]
            FROM [dbo]. [Contatti] AS [Extent1]
        ) AS [Progetto1]
        WHERE ([Project1]. [CompanyId] = @ p__linq__0) AND ([Project1]. [ContactStatusId] <= 3) AND (0 = [Project1]. [C1])
    ) AS [Project2]
) AS [Project2]
DOVE [Project2]. [Row_number]> 99
ORDER BY [Project2]. [ContactId] ASC ', N' @ p__linq__0 int ', @ p__linq__0 = 4

Sembra che Where with EXISTS puro funzioni molto peggio del calcolo di Count e quindi di Where with Count == 0.

Fammi sapere se vedete qualche errore nelle mie scoperte. Ciò che può essere rimosso da tutto ciò indipendentemente dalla discussione Any vs Count è che qualsiasi LINQ più complesso è molto meglio quando riscritto come Stored Procedure;).


2
Mi piacerebbe vedere alcuni piani di query SQL generati da ciascuna query linq per ogni scenario.
— Pure.Krome,

43
basato su SQL, tutto ciò che posso dire è: entrambe le query sembrano orribili. Sapevo che c'era un motivo per cui normalmente scrivo il mio TSQL ...
— Marc Gravell

Tutti dovrebbero guardare attraverso tutte le righe proprio come farebbe Count. Che il tuo esempio dia un risultato così orribile è un po 'strano, nel peggiore dei casi! Qualsiasi dovrebbe essere solo un po' più lento di Count. Nel tuo caso, cercherò modi per semplificare la selezione, magari dividendola per gradi o riordinando le condizioni, se possibile. Ma il tuo punto è che la regola Any è meglio di Count non vale per! Any is better of Count è molto valida.
— Piegato

25

Poiché si tratta di un argomento piuttosto popolare e le risposte sono diverse, ho dovuto dare uno sguardo nuovo al problema.

Test env: EF 6.1.3, SQL Server, 300k record

Modello da tavolo :

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

    public string Name { get; set; }

    public string Surname { get; set; }
}

Codice di prova:

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

risultati:

Qualsiasi () ~ 3ms

Count () ~ 230ms per la prima query, ~ 400ms per la seconda

Osservazioni:

Nel mio caso, EF non ha generato SQL come @Ben menzionato nel suo post.


4
Per un corretto confronto, dovresti farlo Count() > 0. : D
— Andrew,

1
Andrew, Count ()> 0 non funzionerà in modo diverso da Count () in questo particolare test.
— CodeMonkeyForHire

11

EDIT: è stato risolto nella versione EF 6.1.1. e questa risposta non è più attuale

Per SQL Server ed EF4-6, Count () si comporta circa due volte più velocemente di Any ().

Quando esegui Table.Any (), genererà qualcosa di simile ( avviso: non ferire il cervello nel tentativo di capirlo )

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]

che richiede 2 scansioni di righe con la tua condizione.

Non mi piace scrivere Count() > 0perché nasconde la mia intenzione. Preferisco usare predicato personalizzato per questo:

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

Ho notato anche questo. Any () SQL non ha alcun senso. Non sono sicuro del motivo per cui non lo fanno: CASE WHEN (EXISTS (sql)) THEN 1 ELSE 0 END. Non riesco a pensare a un motivo per cui devono fare un NOT EXISTS per restituire 0.
— scott.korin

Questo è falso Hai trovato un piano di query errato per caso. Questo succede. Qualsiasi è, quasi sempre, più veloce.
— usr

Ho controllato il sql generato in 6.1.3, l'hanno risolto: SELEZIONA CASO QUANDO (ESISTE (SELEZIONA 1 COME [C1] DA [dbo]. [TestTables] AS [Estensione1] DOVE [Estensione1]. [Id]> 1000)) POI cast (1 come bit) ELSE cast (0 come bit) END AS [C1] FROM (SELEZIONA 1 AS X) AS [SingleRowTable1]
— Ben

6

Dipende, quanto è grande il set di dati e quali sono i requisiti di prestazione?

Se non è niente di gigantesco, usa la forma più leggibile, che per me è qualsiasi, perché è più breve e leggibile piuttosto che un'equazione.


2

Informazioni sul metodo Count () , se IEnumarable è un ICollection , quindi non possiamo iterare su tutti gli elementi perché possiamo recuperare il campo Count di ICollection , se IEnumerable non è un ICollection dobbiamo iterare su tutti gli elementi usando un po ' di tempo con a MoveNext , dai un'occhiata al codice .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;
}

Riferimento: fonte di riferimento enumerabile


2

Puoi fare un semplice test per capirlo:

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;

Controlla i valori di testCount e testAny.


1
Ecco un test con il tuo codice per la proprietà Count vs Any () la proprietà Count vince vs Any () con + 2x - link
— Stanislav Prusac

1
Per un risultato migliore, potresti fare questi confronti 1000 volte (o più). Aiuta a calcolare la media dei risultati ed evitare picchi casuali.
— Romano

Quando si esegue il test come il metodo sopra menzionato, è necessario considerare molti altri fattori, come il caricamento sul database / rete, la pianificazione della memorizzazione nella cache sul lato database, ecc. Quindi, per eseguire un test accurato, è necessario progettare anche un ambiente isolato e accurato
— Vahid Farahmandian,

per un migliore confronto dovrebbe essere Countsostituito dal metodo Count () vs .Any () non una proprietà. Hai bisogno di tempo di iterazioni.
— daremachine

0

Se stai utilizzando Entity Framework e hai una tabella enorme con molti record, Any () sarà molto più veloce. Ricordo che una volta volevo verificare se una tabella era vuota e conteneva milioni di righe. Il completamento di Count ()> 0 ha richiesto 20-30 secondi. È stato istantaneo con Any () .

Any () può essere un miglioramento delle prestazioni perché potrebbe non essere necessario ripetere la raccolta per ottenere il numero di elementi. Deve solo colpirne uno. O, per esempio, LINQ-to-Entities, l'SQL generato sarà IF EXISTS (...) anziché SELECT COUNT ... o addirittura SELECT * ....

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.