LINQ .Any VS .Exists - Was ist der Unterschied?

413

Was ist der Unterschied zwischen den folgenden Codezeilen bei Verwendung von LINQ für Sammlungen?

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

und

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

Update 1

Wenn ich zerlege .Exists, sieht es so aus, als gäbe es keinen Code.

Update 2

Weiß jemand, warum es für diesen keinen Code gibt?

Anthony D.
quelle
9
Wie sieht der von Ihnen kompilierte Code aus? Wie haben Sie zerlegt? ildasm? Was haben Sie erwartet, aber nicht gefunden?
Meinersbur

Antworten:

423

Siehe Dokumentation

List.Exists (Objektmethode - MSDN)

Legt fest, ob die Liste (T) Elemente enthält, die den durch das angegebene Prädikat definierten Bedingungen entsprechen.

Dies existiert seit .NET 2.0, also vor LINQ. Soll mit dem Prädikat- Delegaten verwendet werden , aber Lambda-Ausdrücke sind abwärtskompatibel. Auch nur List hat dies (nicht einmal IList)

IEnumerable.Any (Erweiterungsmethode - MSDN)

Bestimmt, ob ein Element einer Sequenz eine Bedingung erfüllt.

Dies ist neu in .NET 3.5 und verwendet Func (TSource, bool) als Argument. Daher sollte dies mit Lambda-Ausdrücken und LINQ verwendet werden.

Im Verhalten sind diese identisch.

Meinersbur
quelle
4
Ich habe später einen Beitrag in einem anderen Thread verfasst, in dem ich alle Linq-Äquivalente der .NET 2- List<>Instanzmethoden aufgelistet habe .
Jeppe Stig Nielsen
201

Der Unterschied besteht darin, dass Any eine Erweiterungsmethode für alle IEnumerable<T>in System.Linq.Enumerable definierten Methoden ist. Es kann auf jedem verwendet werdenIEnumerable<T> Instanz verwendet werden.

Exists scheint keine Erweiterungsmethode zu sein. Ich vermute, dass Coll vom Typ ist List<T>. Wenn ja Exists ist eine Instanzmethode, die Any sehr ähnlich funktioniert.

Kurz gesagt , die Methoden sind im Wesentlichen gleich. Einer ist allgemeiner als der andere.

  • Jeder hat auch eine Überladung, die keine Parameter akzeptiert und einfach nach einem Element in der Aufzählung sucht.
  • Exists hat keine solche Überlastung.
JaredPar
quelle
13
Gut ausgedrückt (+1). List <T> .Exists gibt es seit .Net 2, funktioniert aber nur für generische Listen. IEnumerable <T> .Any wurde in .Net 3 als Erweiterung hinzugefügt, die für jede aufzählbare Sammlung funktioniert. Es gibt auch ähnliche Mitglieder wie List <T> .Count, eine Eigenschaft, und IEnumerable <T> .Count () - eine Methode.
Keith
51

TLDR; In Bezug auf die Leistung Anyscheint es langsamer zu sein (wenn ich dies richtig eingerichtet habe, um beide Werte fast gleichzeitig auszuwerten)

        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"))
            {

            }

Testlistengenerator:

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

Mit 10 Millionen Datensätzen

Any: 00: 00: 00.3770377 Exists: 00: 00: 00.2490249

Mit 5 Millionen Datensätzen

"Any: 00: 00: 00.0940094 Exists: 00: 00: 00.1420142"

Mit 1M Datensätzen

"Any: 00: 00: 00.0180018 Exists: 00: 00: 00.0090009"

Mit 500k (Ich habe auch die Reihenfolge umgedreht, in der sie ausgewertet werden, um festzustellen, ob keine zusätzliche Operation mit der zuerst ausgeführten Operation verbunden ist.)

"Exists: 00: 00: 00.0050005 Any: 00: 00: 00.0100010"

Mit 100.000 Datensätzen

"Exists: 00: 00: 00.0010001 Any: 00: 00: 00.0020002"

Es scheint Anyum die Größe 2 langsamer zu sein.

Bearbeiten: Bei 5 und 10 Millionen Datensätzen habe ich die Art und Weise geändert, in der die Liste erstellt wird, und bin Existsplötzlich langsamer geworden, als Anydies impliziert, dass beim Testen etwas nicht stimmt.

Neuer Testmechanismus:

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, um jeglichen Einfluss von der Generierung von Testdaten auszuschließen, habe ich alles in eine Datei geschrieben und jetzt von dort gelesen.

 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

"Any: 00: 00: 00.1640164 Exists: 00: 00: 00.0750075"

5M

"Beliebig: 00: 00: 00.0810081 Exists: 00: 00: 00.0360036"

1M

"Any: 00: 00: 00.0190019 Exists: 00: 00: 00.0070007"

500k

"Any: 00: 00: 00.0120012 Exists: 00: 00: 00.0040004"

Geben Sie hier die Bildbeschreibung ein

Matas Vaitkevicius
quelle
3
Keine Diskreditierung für Sie, aber ich bin skeptisch gegenüber diesen Benchmarks. Schauen Sie sich die Zahlen an: Bei jedem Ergebnis findet eine Rekursion statt (3770377: 2490249). Zumindest für mich ist das ein sicheres Zeichen, dass etwas nicht stimmt. Ich bin mir in der Mathematik hier nicht hundertprozentig sicher, aber ich denke, die Wahrscheinlichkeit, dass dieses wiederkehrende Muster auftritt, beträgt 1 zu 999 ^ 999 (oder 999! Vielleicht?) Pro Wert. Die Wahrscheinlichkeit, dass es 8 Mal hintereinander passiert, ist also infinitesimal. Ich denke, das liegt daran, dass Sie DateTime für das Benchmarking verwenden .
Jerri Kangasniemi
@JerriKangasniemi Das isolierte Wiederholen derselben Operation sollte immer dieselbe Zeit in Anspruch nehmen. Gleiches gilt für das mehrfache Wiederholen. Warum sagst du, dass es DateTime ist?
Matas Vaitkevicius
Natürlich tut es das. Das Problem ist immer noch, dass es äußerst unwahrscheinlich ist, dass beispielsweise 500.000 Anrufe 0120012 Sekunden dauern. Und wenn es perfekt linear wäre und die Zahlen so gut erklärt, hätten 1M-Anrufe 0240024 Sekunden (doppelt so lange) gedauert, aber das ist nicht der Fall. 1M Anrufe dauern 58, (3)% länger als 500k und 10M dauert 102,5% länger als 5M. Es ist also keine lineare Funktion und daher für die Zahlen nicht wirklich vernünftig. Ich habe DateTime erwähnt, weil ich in der Vergangenheit selbst Probleme damit hatte, weil DateTime keine hochpräzisen Timer verwendet.
Jerri Kangasniemi
2
@JerriKangasniemi Könnte ich vorschlagen, dass Sie es beheben und eine Antwort posten
Matas Vaitkevicius
1
Wenn ich Ihre Ergebnisse richtig lese, haben Sie angegeben, dass Any nur etwa zwei- bis dreimal so schnell ist wie Exists. Ich verstehe nicht, wie die Daten Ihre Behauptung, dass "Any um eine Größenordnung von 2 langsamer zu sein scheint", auch nur geringfügig unterstützen. Es ist sicher etwas langsamer, nicht um Größenordnungen.
Suncat2000
16

Als Fortsetzung der Antwort von Matas zum Benchmarking.

TL / DR : Exists () und Any () sind gleich schnell.

Zunächst einmal: Das Benchmarking mit Stoppuhr ist nicht präzise ( siehe die Antwort von series0ne zu einem anderen, aber ähnlichen Thema ), aber weitaus präziser als DateTime.

Der Weg, um wirklich genaue Messwerte zu erhalten, ist die Verwendung von Leistungsprofilen. Eine Möglichkeit, ein Gefühl dafür zu bekommen, wie sich die Leistung der beiden Methoden gegenseitig misst, besteht darin, beide Methoden viele Male auszuführen und dann die jeweils schnellste Ausführungszeit zu vergleichen. Auf diese Weise spielt es wirklich keine Rolle, dass JITing und anderes Rauschen zu schlechten Messwerten führen (und das tut es auch ), da beide Ausführungen in gewissem Sinne " gleichermaßen falsch " sind.

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

Nach viermaliger Ausführung des obigen Codes (die wiederum 1 000 Exists()und Any()eine Liste mit 1 000 000 Elementen enthält) ist es nicht schwer zu erkennen, dass die Methoden ziemlich gleich schnell sind.

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

Es gibt einen kleinen Unterschied, aber er ist zu klein, um nicht durch Hintergrundgeräusche erklärt zu werden. Meine Vermutung wäre, wenn man 10 000 oder 100 000 machen würde Exists()und Any()stattdessen dieser kleine Unterschied mehr oder weniger verschwinden würde.

Jerri Kangasniemi
quelle
Könnte ich vorschlagen, dass Sie 10 000 und 100 000 und 1000000 machen, nur um methodisch zu sein, auch warum min und nicht Durchschnittswert?
Matas Vaitkevicius
2
Der Mindestwert liegt darin, dass ich die schnellste Ausführung (= wahrscheinlich das geringste Hintergrundrauschen) jeder Methode vergleichen möchte. Ich könnte es mit mehr Iterationen tun, obwohl es später sein wird (ich bezweifle, dass mein Chef mich dafür bezahlen möchte, anstatt unseren Rückstand zu
durcharbeiten
Ich habe Paul Lindberg gefragt und er sagt, es sei in Ordnung;) In Bezug auf das Minimum kann ich Ihre Argumentation sehen, jedoch ist es orthodoxer, den Durchschnitt zu verwenden. En.wikipedia.org/wiki/Algorithmic_efficiency#Practice
Matas Vaitkevicius
9
Wenn der von Ihnen gepostete Code derjenige ist, den Sie tatsächlich ausgeführt haben, ist es nicht überraschend, dass Sie ähnliche Ergebnisse erhalten, da Sie in beiden Messungen Exists aufrufen. ;)
Simon Touchtech
Heh, ja, das habe ich auch gesehen, jetzt sagst du es. Nicht in meiner Hinrichtung. Dies war nur als abgespecktes Konzept dessen gedacht, was ich verglich. : P
Jerri Kangasniemi
4

Darüber hinaus funktioniert dies nur, wenn Value vom Typ bool ist. Normalerweise wird dies mit Prädikaten verwendet. Jedes Prädikat würde im Allgemeinen verwendet, um festzustellen, ob es ein Element gibt, das eine gegebene Bedingung erfüllt. Hier machen Sie einfach eine Map von Ihrem Element i zu einer bool-Eigenschaft. Es wird nach einem "i" gesucht, dessen Value-Eigenschaft wahr ist. Sobald dies erledigt ist, gibt die Methode true zurück.

flq
quelle
3

Wenn Sie die Messungen korrigieren - wie oben erwähnt: Beliebig und Existiert und Durchschnitt addiert - erhalten wir folgende Ausgabe:

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.
jasmintmp
quelle