LINQ .Any VS .Exists - Quelle est la différence?


413

En utilisant LINQ sur des collections, quelle est la différence entre les lignes de code suivantes?

if(!coll.Any(i => i.Value))

et

if(!coll.Exists(i => i.Value))

Mise à jour 1

Quand je démonte, .Existsil semble qu'il n'y ait pas de code.

Update 2

Quelqu'un sait pourquoi il n'y a pas de code pour celui-ci?


9
À quoi ressemble le code que vous avez compilé? Comment avez-vous démonté? ildasm? Que vous attendiez-vous à trouver mais que vous n'avez pas trouvé?
Meinersbur

Réponses:


423

Voir documentation

List.Exists (méthode Object - MSDN)

Détermine si la liste (T) contient des éléments qui correspondent aux conditions définies par le prédicat spécifié.

Cela existe depuis .NET 2.0, donc avant LINQ. Destiné à être utilisé avec le délégué Predicate , mais les expressions lambda sont rétrocompatibles. Aussi, juste List a ceci (pas même IList)

IEnumerable.Any (méthode d'extension - MSDN)

Détermine si un élément d'une séquence satisfait à une condition.

Ceci est nouveau dans .NET 3.5 et utilise Func (TSource, bool) comme argument, il était donc destiné à être utilisé avec des expressions lambda et LINQ.

En comportement, ceux-ci sont identiques.


4
Plus tard, j'ai fait un post dans un autre thread où j'ai répertorié tous les «équivalents» Linq des List<>méthodes d'instance .NET 2 .
Jeppe Stig Nielsen

201

La différence est que Any est une méthode d'extension pour tout IEnumerable<T>défini sur System.Linq.Enumerable. Il peut être utilisé sur n'importe quelle IEnumerable<T>instance.

Exists ne semble pas être une méthode d'extension. Je suppose que coll est de type List<T>. Si c'est le cas, Exists est une méthode d'instance qui fonctionne de manière très similaire à Any.

Bref , les méthodes sont essentiellement les mêmes. L'un est plus général que l'autre.

  • Any a également une surcharge qui ne prend aucun paramètre et recherche simplement n'importe quel élément dans l'énumérable.
  • Existe n'a pas une telle surcharge.

13
Bien mis (+1). List <T> .Exists existe depuis .Net 2 mais ne fonctionne que pour les listes génériques. IEnumerable <T> .Any a été ajouté dans .Net 3 en tant qu'extension qui fonctionne sur n'importe quelle collection énumérable. Il existe également des membres similaires comme List <T> .Count, qui est une propriété et IEnumerable <T> .Count () - une méthode.
Keith

51

TLDR; Les performances Anysemblent être plus lentes (si j'ai bien configuré cela pour évaluer les deux valeurs presque en même temps)

        var list1 = Generate(1000000);
        var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
        if (forceListEval != "sdsdf")
        {
            var s = string.Empty;
            var start2 = DateTime.Now;
            if (!list1.Exists(o => o == "0123456789012"))
            {
                var end2 = DateTime.Now;
                s += " Exists: " + end2.Subtract(start2);
            }

            var start1 = DateTime.Now;
            if (!list1.Any(o => o == "0123456789012"))
            {
                var end1 = DateTime.Now;
                s +=" Any: " +end1.Subtract(start1);
            }

            if (!s.Contains("sdfsd"))
            {

            }

générateur de liste de tests:

private List<string> Generate(int count)
    {
        var list = new List<string>();
        for (int i = 0; i < count; i++)
        {
            list.Add( new string(
            Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 13)
                .Select(s =>
                {
                    var cryptoResult = new byte[4];
                    new RNGCryptoServiceProvider().GetBytes(cryptoResult);
                    return s[new Random(BitConverter.ToInt32(cryptoResult, 0)).Next(s.Length)];
                })
                .ToArray())); 
        }

        return list;
    }

Avec 10 millions d'enregistrements

"N'importe lequel: 00: 00: 00.3770377 Existe: 00: 00: 00.2490249"

Avec 5 millions d'enregistrements

"N'importe lequel: 00: 00: 00.0940094 Existe: 00: 00: 00.1420142"

Avec 1 million d'enregistrements

"N'importe lequel: 00: 00: 00.0180018 Existe: 00: 00: 00.0090009"

Avec 500k, (j'ai également inversé l'ordre dans lequel ils sont évalués pour voir s'il n'y a aucune opération supplémentaire associée à celle qui s'exécute en premier.)

"Existe: 00: 00: 00.0050005 N'importe lequel: 00: 00: 00.0100010"

Avec 100 000 enregistrements

"Existe: 00: 00: 00.0010001 N'importe lequel: 00: 00: 00.0020002"

Il semblerait Anyqu'il soit plus lent d'une magnitude de 2.

Edit: Pour les enregistrements de 5 et 10M, j'ai changé la façon dont il génère la liste et je suis Existssoudainement devenu plus lent queAny je suis ce qui implique qu'il y a quelque chose de mal dans la façon dont je teste.

Nouveau mécanisme de test:

private static IEnumerable<string> Generate(int count)
    {
        var cripto = new RNGCryptoServiceProvider();
        Func<string> getString = () => new string(
            Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 13)
                .Select(s =>
                {
                    var cryptoResult = new byte[4];
                    cripto.GetBytes(cryptoResult);
                    return s[new Random(BitConverter.ToInt32(cryptoResult, 0)).Next(s.Length)];
                })
                .ToArray());

        var list = new ConcurrentBag<string>();
        var x = Parallel.For(0, count, o => list.Add(getString()));
        return list;
    }

    private static void Test()
    {
        var list = Generate(10000000);
        var list1 = list.ToList();
        var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
        if (forceListEval != "sdsdf")
        {
            var s = string.Empty;

            var start1 = DateTime.Now;
            if (!list1.Any(o => o == "0123456789012"))
            {
                var end1 = DateTime.Now;
                s += " Any: " + end1.Subtract(start1);
            }

            var start2 = DateTime.Now;
            if (!list1.Exists(o => o == "0123456789012"))
            {
                var end2 = DateTime.Now;
                s += " Exists: " + end2.Subtract(start2);
            }

            if (!s.Contains("sdfsd"))
            {

            }
        }

Edit2: Ok donc pour éliminer toute influence de la génération de données de test, j'ai tout écrit dans un fichier et maintenant je le lis à partir de là.

 private static void Test()
    {
        var list1 = File.ReadAllLines("test.txt").Take(500000).ToList();
        var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
        if (forceListEval != "sdsdf")
        {
            var s = string.Empty;
            var start1 = DateTime.Now;
            if (!list1.Any(o => o == "0123456789012"))
            {
                var end1 = DateTime.Now;
                s += " Any: " + end1.Subtract(start1);
            }

            var start2 = DateTime.Now;
            if (!list1.Exists(o => o == "0123456789012"))
            {
                var end2 = DateTime.Now;
                s += " Exists: " + end2.Subtract(start2);
            }

            if (!s.Contains("sdfsd"))
            {
            }
        }
    }

10M

"N'importe lequel: 00: 00: 00.1640164 Existe: 00: 00: 00.0750075"

5M

"N'importe lequel: 00: 00: 00.0810081 Existe: 00: 00: 00.0360036"

1M

"Tout: 00: 00: 00.0190019 Existe: 00: 00: 00.0070007"

500k

"N'importe lequel: 00: 00: 00.0120012 Existe: 00: 00: 00.0040004"

entrez la description de l'image ici


3
Pas de discrédit pour vous, mais je me sens sceptique sur ces repères. Regardez les chiffres: chaque résultat a une récurrence (3770377: 2490249). Du moins pour moi, c'est un signe certain que quelque chose n'est pas correct. Je ne suis pas sûr à 100% des calculs ici, mais je pense que la probabilité que ce modèle récurrent se produise est de 1 sur 999 ^ 999 (ou 999! Peut-être?) Par valeur. La probabilité que cela se produise 8 fois de suite est donc infinitésimale. Je pense que c'est parce que vous utilisez DateTime pour l'analyse comparative .
Jerri Kangasniemi

@JerriKangasniemi Répéter la même opération isolément devrait toujours prendre le même temps, il en va de même pour la répéter plusieurs fois. Qu'est-ce qui vous fait dire que c'est DateTime?
Matas Vaitkevicius

Bien sûr que oui. Le problème est toujours qu'il est extrêmement improbable de prendre par exemple 0120012 secondes pour les appels de 500k. Et s'il était parfaitement linéaire, expliquant ainsi les chiffres si bien, les appels 1M auraient pris 0240024 secondes (deux fois plus longtemps), mais ce n'est pas le cas. 1M d'appels prend 58, (3)% de plus que 500k et 10M prend 102,5% de plus que 5M. Ce n'est donc pas une fonction linéaire et donc pas vraiment raisonnable pour les chiffres de tous recurer. J'ai mentionné DateTime parce que j'ai moi-même rencontré des problèmes avec lui par le passé, parce que DateTime n'utilisait pas de minuteries de haute précision.
Jerri Kangasniemi

2
@JerriKangasniemi Puis-je vous suggérer de le corriger et de poster une réponse
Matas Vaitkevicius

1
Si je lis correctement vos résultats, vous avez indiqué que Any n'était que 2 à 3 fois plus rapide qu'Exists. Je ne vois pas comment les données soutiennent même légèrement votre affirmation selon laquelle "il semblerait que Any soit plus lent par une amplitude de 2". C'est un peu plus lent, bien sûr, pas des ordres de grandeur.
Suncat2000

16

Dans la continuité de la réponse de Matas sur l'analyse comparative.

TL / DR : Exists () et Any () sont également rapides.

Tout d'abord: l'analyse comparative à l'aide du chronomètre n'est pas précise ( voir la réponse de series0ne sur un sujet différent, mais similaire) ), mais elle est beaucoup plus précise que DateTime.

La façon d'obtenir des lectures vraiment précises est d'utiliser le profilage des performances. Mais une façon de se faire une idée de la façon dont les performances des deux méthodes se mesurent est d'exécuter des charges de temps des deux méthodes , puis de comparer le temps d'exécution le plus rapide de chacune. De cette façon, peu importe que le JIT et d'autres bruits nous donnent de mauvaises lectures (et c'est le cas ), parce que les deux exécutions sont " également erronées " dans un sens.

static void Main(string[] args)
    {
        Console.WriteLine("Generating list...");
        List<string> list = GenerateTestList(1000000);
        var s = string.Empty;

        Stopwatch sw;
        Stopwatch sw2;
        List<long> existsTimes = new List<long>();
        List<long> anyTimes = new List<long>();

        Console.WriteLine("Executing...");
        for (int j = 0; j < 1000; j++)
        {
            sw = Stopwatch.StartNew();
            if (!list.Exists(o => o == "0123456789012"))
            {
                sw.Stop();
                existsTimes.Add(sw.ElapsedTicks);
            }
        }

        for (int j = 0; j < 1000; j++)
        {
            sw2 = Stopwatch.StartNew();
            if (!list.Exists(o => o == "0123456789012"))
            {
                sw2.Stop();
                anyTimes.Add(sw2.ElapsedTicks);
            }
        }

        long existsFastest = existsTimes.Min();
        long anyFastest = anyTimes.Min();

        Console.WriteLine(string.Format("Fastest Exists() execution: {0} ticks\nFastest Any() execution: {1} ticks", existsFastest.ToString(), anyFastest.ToString()));
        Console.WriteLine("Benchmark finished. Press any key.");
        Console.ReadKey();
    }

    public static List<string> GenerateTestList(int count)
    {
        var list = new List<string>();
        for (int i = 0; i < count; i++)
        {
            Random r = new Random();
            int it = r.Next(0, 100);
            list.Add(new string('s', it));
        }
        return list;
    }

Après avoir exécuté le code ci-dessus 4 fois (qui à son tour font 1 000 Exists()et Any()sur une liste avec 1 000 000 éléments), il n'est pas difficile de voir que les méthodes sont à peu près aussi rapides.

Fastest Exists() execution: 57881 ticks
Fastest Any() execution: 58272 ticks

Fastest Exists() execution: 58133 ticks
Fastest Any() execution: 58063 ticks

Fastest Exists() execution: 58482 ticks
Fastest Any() execution: 58982 ticks

Fastest Exists() execution: 57121 ticks
Fastest Any() execution: 57317 ticks

Il y a une légère différence, mais c'est une différence trop petite pour ne pas être expliquée par le bruit de fond. Je suppose que si l'on faisait 10 000 ou 100 000 Exists()et à la Any()place, cette légère différence disparaîtrait plus ou moins.


Puis-je vous suggérer de faire 10 000 et 100 000 et 1000000, juste pour être méthodique à ce sujet, aussi pourquoi la valeur minimale et non moyenne?
Matas Vaitkevicius

2
La valeur minimale est parce que je veux comparer l'exécution la plus rapide (= probablement le moins de bruit de fond) de chaque méthode. Je pourrais le faire avec plus d'itérations, mais ce sera plus tard (je doute que mon patron veuille me payer pour cela au lieu de parcourir notre carnet de commandes)
Jerri Kangasniemi

J'ai demandé à Paul Lindberg et il dit que c'est ok;) en ce qui concerne le minimum, je peux voir votre raisonnement, mais l'approche la plus orthodoxe consiste à utiliser la moyenne en.wikipedia.org/wiki/Algorithmic_efficiency#Practice
Matas Vaitkevicius

9
Si le code que vous avez publié est celui que vous avez réellement exécuté, il n'est pas surprenant que vous obteniez des résultats similaires, car vous appelez Exists dans les deux mesures. ;)
Simon Touchtech

Hé, ouais, j'ai vu ça aussi maintenant tu le dis. Pas dans mon exécution cependant. C'était juste un concept dépouillé de ce que je comparais. : P
Jerri Kangasniemi

4

De plus, cela ne fonctionnera que si Value est de type bool. Normalement, cela est utilisé avec des prédicats. Tout prédicat serait généralement utilisé pour trouver s'il existe un élément satisfaisant une condition donnée. Ici, vous faites simplement une carte de votre élément i vers une propriété bool. Il recherchera un «i» dont la propriété Value est vraie. Une fois cela fait, la méthode retournera true.


3

Lorsque vous corrigez les mesures - comme mentionné ci-dessus: Any et Exists, et en ajoutant la moyenne - nous obtiendrons la sortie suivante:

Executing search Exists() 1000 times ... 
Average Exists(): 35566,023
Fastest Exists() execution: 32226 

Executing search Any() 1000 times ... 
Average Any(): 58852,435
Fastest Any() execution: 52269 ticks

Benchmark finished. Press any key.
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.