Quelle méthode fonctionne mieux: .Any () vs .Count ()> 0?

578

dans l' System.Linqespace de noms, nous pouvons maintenant étendre nos IEnumerables pour avoir les méthodes d'extension Any () et Count () .

On m'a dit récemment que si je veux vérifier qu'une collection contient 1 ou plusieurs éléments à l'intérieur, je devrais utiliser la .Any()méthode d'extension au lieu de la .Count() > 0méthode d'extension car la .Count()méthode d'extension doit parcourir tous les éléments.

Deuxièmement, certaines collections ont une propriété (pas une méthode d'extension) qui est Countou Length. Serait-il préférable de les utiliser à la place de .Any()ou .Count()?

oui / nae?

Pure.Krome
la source
Mieux vaut utiliser Any () sur les énumérables et compter sur les collections. Si quelqu'un sent que l'écriture de '' (somecollection.Count> 0) 'confondra ou causera des problèmes de lisibilité, il vaut mieux l'écrire en tant que méthode d'extension nommez Any (). Ensuite, tout le monde était satisfait. En termes de performances et de lisibilité. Pour que tout votre code soit cohérent et que le développeur individuel de votre projet n'ait pas à se soucier de choisir Count vs Any.
Mahesh Bongani
Vous avez vu Count ()> 0 vs Any (), mais avez-vous vu Distinct (). Count ()> 1 vs Distinct (). Skip (1) .Any ()? Ce dernier est certainement plus rapide pour un grand nombre d'articles où Count doit en fait itérer sur l'ensemble pour obtenir un compte. Skip (1) .Any () évite l'énumération complète. 100 000 itérations de la vérification d'un tableau de chaînes de 1 000 éléments avec 1 chaîne de caractères qui s'exécute en environ 4000 ms pour Count ()> 1, s'exécute en seulement 20 ms pour Skip (1) .Any ().
Triynko Il y a

Réponses:

710

Si vous commencez par quelque chose qui a un .Lengthou .Count(commeICollection<T> , IList<T>, List<T>, etc.) - alors ce sera l'option la plus rapide, car il n'a pas besoin de passer par la GetEnumerator()/ MoveNext()/ Dispose()séquence requise par Any()vérifier pour un non-videIEnumerable<T> séquence .

Pour seulement IEnumerable<T>, puis Any()sera généralement plus rapide, car il n'a qu'à regarder une seule itération. Cependant, notez que l'implémentation LINQ-to-Objects Count()vérifie ICollection<T>(en .Counttant qu'optimisation) - donc si votre source de données sous-jacente est directement une liste / collection, il n'y aura pas de différence énorme. Ne me demandez pas pourquoi il n'utilise pas le non génériqueICollection ...

Bien sûr, si vous avez utilisé LINQ pour le filtrer, etc. ( Whereetc), vous aurez une séquence basée sur un bloc d'itérateur, et donc cette ICollection<T>optimisation est inutile.

En général avec IEnumerable<T>: coller avec Any();-p

Marc Gravell
la source
9
Marc: ICollection <T> ne dérive pas réellement d'ICollection. J'ai également été surpris, mais Reflector ne ment pas.
Bryan Watts
7
L'implémentation Any () ne vérifie-t-elle pas l'interface ICollection et ne vérifie-t-elle pas la propriété Count?
derigel
313
Je pense qu'il y a une autre raison d'utiliser Any () la plupart du temps. Il signale l'intention précise du développeur. Si vous n'êtes pas intéressé à connaître le nombre d'articles, mais seulement s'il y en a, alors somecollection.Any () est plus simple et plus clair que somecollection.Count> 0
TJKjaer
13
@huttelihut - Combien de développeurs connaissez-vous qui sont vraiment confus par la déclaration (somecollection.Count > 0)? Tout notre code avant l'introduction de la méthode .Any () de LINQ était-il difficile à comprendre?
CraigTP
25
@JLRishe - Je pense toujours que someCollection.Count > 0c'est aussi clair que someCollection.Any()et a l'avantage supplémentaire de meilleures performances et de ne pas nécessiter LINQ. Certes, il s'agit d'un cas très simple et d'autres constructions utilisant des opérateurs LINQ véhiculeront l'intention des développeurs beaucoup plus clairement que l'option non LINQ équivalente.
CraigTP
65

Remarque: J'ai écrit cette réponse lorsque Entity Framework 4 était réel. Le but de cette réponse n'était pas d'entrer dans des tests triviaux .Any()contre des .Count()performances. Le but était de signaler que EF est loin d'être parfait. Les versions plus récentes sont meilleures ... mais si vous avez une partie de code qui est lente et utilise EF, testez avec TSQL direct et comparez les performances plutôt que de vous fier à des hypothèses (c'est .Any()TOUJOURS plus rapide que .Count() > 0).


Bien que je sois d'accord avec la réponse et les commentaires les plus votés - en particulier sur le point Any, l' intention du développeur est meilleure queCount() > 0 - j'ai eu une situation dans laquelle Count est plus rapide par ordre de grandeur sur SQL Server (EntityFramework 4).

Voici la requête avec Anycette exception de délai d'attente (sur environ 200 000 enregistrements):

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 exécutée en quelques millisecondes:

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

J'ai besoin de trouver un moyen de voir quel SQL exact les deux LINQ produisent - mais il est évident qu'il y a une énorme différence de performances entre Countet Anydans certains cas, et malheureusement il semble que vous ne pouvez pas simplement vous en tenir Anyà tous les cas.

EDIT: Voici les SQL générés. Des beautés comme vous pouvez le voir;)

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]. [Créé] AS [Créé]
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]
    DE (CHOISIR 
        [Extent1]. [ContactId] AS [ContactId], 
        [Extent1]. [CompanyId] AS [CompanyId], 
        [Extent1]. [ContactName] AS [ContactName], 
        [Extent1]. [FullName] AS [FullName], 
        [Extent1]. [ContactStatusId] AS [ContactStatusId], 
        [Extent1]. [Créé] AS [Créé]
        DE [dbo]. [Contact] AS [Extent1]
        OERE ([Extent1]. [CompanyId] = @ p__linq__0) ET ([Extent1]. [ContactStatusId] <= 3) ET (PAS EXISTANT (SELECT 
            1 AS [C1]
            DE [dbo]. [NewsletterLog] AS [Extent2]
            OERE ([Extent1]. [ContactId] = [Extent2]. [ContactId]) ET (6 = [Extent2]. [NewsletterLogTypeId])
        ))
    ) AS [Project2]
) AS [Project2]
OERE [Project2]. [Row_number]> 99
COMMANDER PAR [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]. [Créé] AS [Créé]
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]
    DE (CHOISIR 
        [Project1]. [ContactId] AS [ContactId], 
        [Project1]. [CompanyId] AS [CompanyId], 
        [Project1]. [ContactName] AS [ContactName], 
        [Project1]. [FullName] AS [FullName], 
        [Project1]. [ContactStatusId] AS [ContactStatusId], 
        [Project1]. [Créé] AS [Créé]
        DE (CHOISIR 
            [Extent1]. [ContactId] AS [ContactId], 
            [Extent1]. [CompanyId] AS [CompanyId], 
            [Extent1]. [ContactName] AS [ContactName], 
            [Extent1]. [FullName] AS [FullName], 
            [Extent1]. [ContactStatusId] AS [ContactStatusId], 
            [Extent1]. [Créé] COMME [Créé], 
            (SÉLECTIONNER 
                COUNT (1) AS [A1]
                DE [dbo]. [NewsletterLog] AS [Extent2]
                OERE ([Extent1]. [ContactId] = [Extent2]. [ContactId]) ET (6 = [Extent2]. [NewsletterLogTypeId])) AS [C1]
            DE [dbo]. [Contact] AS [Extent1]
        ) AS [Project1]
        OERE ([Project1]. [CompanyId] = @ p__linq__0) AND ([Project1]. [ContactStatusId] <= 3) AND (0 = [Project1]. [C1])
    ) AS [Project2]
) AS [Project2]
OERE [Project2]. [Row_number]> 99
COMMANDER PAR [Project2]. [ContactId] ASC ', N' @ p__linq__0 int ', @ p__linq__0 = 4

Il semble que pur Where avec EXISTS fonctionne bien pire que de calculer Count puis de faire Where with Count == 0.

Faites-moi savoir si vous voyez des erreurs dans mes conclusions. Ce qui peut être retiré de tout cela indépendamment de la discussion Any vs Count, c'est que tout LINQ plus complexe est beaucoup mieux lorsqu'il est réécrit en tant que procédure stockée;).

nikib3ro
la source
2
J'adorerais voir certains plans de requête SQL générés par chaque requête linq pour chaque scénario.
Pure.Krome
43
basé sur le SQL, tout ce que je peux dire est: les deux requêtes ont l'air horribles. Je savais qu'il y avait une raison pour laquelle j'écris normalement mon propre TSQL ...
Marc Gravell
! N'importe qui devrait parcourir toutes les lignes comme le ferait Count. Que votre exemple donne un résultat aussi horrible est un peu étrange, dans le pire des cas! N'importe lequel ne devrait être qu'un peu plus lent que Count. Dans votre cas, je chercherais des moyens de simplifier la sélection, peut-être en la divisant par étapes ou en réorganisant les conditions si cela est possible. Mais votre argument selon lequel la règle Any is better than Count ne tient pas! Any is better than Count est très bon.
Bent
25

Comme c'est un sujet assez populaire et que les réponses diffèrent, j'ai dû jeter un regard neuf sur le problème.

Test d'env: EF 6.1.3, SQL Server, 300k enregistrements

Modèle de table :

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

    public string Name { get; set; }

    public string Surname { get; set; }
}

Code de test:

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

Résultats:

Tout () ~ 3 ms

Count () ~ 230 ms pour la première requête, ~ 400 ms pour la seconde

Remarques:

Pour mon cas, EF n'a pas généré de SQL comme @Ben mentionné dans son article.

kamil-mrzyglod
la source
4
Pour une comparaison correcte, vous devriez le faire Count() > 0. : D
Andrew
1
Andrew, Count ()> 0 ne fonctionnera pas différemment de Count () dans ce test particulier.
CodeMonkeyForHire
11

EDIT: il a été corrigé dans EF version 6.1.1. et cette réponse n'est plus d'actualité

Pour SQL Server et EF4-6, Count () fonctionne environ deux fois plus rapidement que Any ().

Lorsque vous exécutez Table.Any (), cela générera quelque chose comme ( alerte: ne blessez pas le cerveau en essayant de le comprendre )

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]

cela nécessite 2 scans de lignes avec votre condition.

Je n'aime pas écrire Count() > 0car cela cache mon intention. Je préfère utiliser un prédicat personnalisé pour cela:

public static class QueryExtensions
{
    public static bool Exists<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
    {
        return source.Count(predicate) > 0;
    }
}
Ben
la source
Je l'ai remarqué aussi. Le SQL Any () n'a aucun sens. Je ne sais pas pourquoi ils ne le font pas: CAS WHEN (EXISTS (sql)) THEN 1 ELSE 0 END. Je ne peux pas penser à une raison pour laquelle ils doivent faire un NOT EXISTS afin de retourner 0.
scott.korin
C'est faux. Vous avez trouvé un mauvais plan de requête par hasard. Ça arrive. Tout est, presque toujours, plus rapide.
usr
J'ai vérifié le SQL généré en 6.1.3, ils l'ont corrigé: SELECT CASE WHEN (EXISTS (SELECT 1 AS [C1] FROM [dbo]. [TestTables] AS [Extent1] WHERE [Extent1]. [Id]> 1000)) ALORS cast (1 comme bit) ELSE cast (0 comme bit) END AS [C1] FROM (SELECT 1 AS X) AS [SingleRowTable1]
Ben
6

Cela dépend, quelle est la taille de l'ensemble de données et quelles sont vos exigences de performance?

Si ce n'est rien de gigantesque, utilisez la forme la plus lisible, qui est pour moi, car elle est plus courte et lisible plutôt qu'une équation.

Timothy Gonzalez
la source
2

À propos de la méthode Count () , si le IEnumarable est un ICollection , nous ne pouvons pas parcourir tous les éléments car nous pouvons récupérer le champ Count de ICollection , si le IEnumerable n'est pas un ICollection, nous devons parcourir tous les éléments en utilisant un moment avec un MoveNext , jetez un œil au code .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;
}

Référence: source de référence énumérable

Thiago Coelho
la source
2

Vous pouvez faire un test simple pour comprendre cela:

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;

Vérifiez les valeurs de testCount et testAny.

Bronks
la source
1
Voici un test avec votre code pour la propriété Count vs Any () La propriété Count gagne vs Any () avec + 2x - lien
Stanislav Prusac
1
Pour un meilleur résultat, vous pouvez effectuer ces comparaisons 1000 fois (ou plus). Cela permet de faire la moyenne des résultats et d'éviter tout pic aléatoire.
Roman
Lorsque vous testez comme la méthode mentionnée ci-dessus, vous devez prendre en compte de nombreux autres facteurs, tels que la charge sur votre base de données / réseau, planifier la mise en cache côté base de données, etc. Donc, pour faire un test précis, vous devez également concevoir un environnement isolé et précis.
Vahid Farahmandian
pour une meilleure comparaison, la Countméthode Count () vs .Any () n'est pas une propriété. Vous avez besoin de temps d'itérations.
daremachine
0

Si vous utilisez Entity Framework et disposez d'une énorme table avec de nombreux enregistrements, Any () sera beaucoup plus rapide. Je me souviens d'une fois où j'ai voulu vérifier si une table était vide et qu'elle avait des millions de lignes. Il a fallu 20 à 30 secondes pour que Count ()> 0 se termine. C'était instantané avec Any () .

Any () peut être une amélioration des performances car il peut ne pas avoir à itérer la collection pour obtenir le nombre de choses. Il suffit de frapper l'un d'eux. Ou, pour, disons, LINQ-to-Entities, le SQL généré sera IF EXISTS (...) plutôt que SELECT COUNT ... ou même SELECT * ....

Janmejay Kumar
la source