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.