LINQ: Nicht alle gegen alle nicht

272

Oft möchte ich überprüfen, ob ein angegebener Wert mit einem in einer Liste übereinstimmt (z. B. bei der Validierung):

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

Kürzlich habe ich festgestellt, dass ReSharper mich gebeten hat, diese Abfragen zu vereinfachen, um:

if (acceptedValues.All(v => v != someValue))
{
    // exception logic
}

Offensichtlich ist dies logisch identisch, vielleicht etwas besser lesbar (wenn Sie viel Mathematik gemacht haben). Meine Frage lautet: Führt dies zu einem Leistungseinbruch?

Es fühlt sich so an, wie es sollte (dh es .Any()klingt so, als würde es kurzschließen, während es so .All()klingt, als ob es nicht so wäre), aber ich habe nichts, um dies zu begründen. Hat jemand ein tieferes Wissen darüber, ob die Abfragen dasselbe lösen oder ob ReSharper mich in die Irre führt?

Kennzeichen
quelle
6
Haben Sie versucht, den Linq-Code zu zerlegen, um zu sehen, was er tut?
RQDQ
9
In diesem Fall würde ich tatsächlich mit if (! AcceptedValues.Contains (someValue)) gehen, aber das war natürlich nicht die Frage :)
csgero
2
@csgero Ich stimme zu. Das Obige war eine Vereinfachung (vielleicht eine übermäßige Vereinfachung) der wirklichen Logik.
Mark
1
"Es fühlt sich so an, als ob es sollte (dh .Any () klingt so, als würde es kurzschließen, während .All () so klingt, als ob es nicht so wäre)" - Nicht für jemanden mit Sound-Intuitionen. Die logische Äquivalenz, die Sie bemerken, impliziert, dass sie gleichermaßen kurzschlussfähig sind. Ein kurzer Gedanke zeigt, dass Alle aufhören können, sobald ein nicht qualifizierender Fall auftritt.
Jim Balter
3
Ich stimme ReSharper diesbezüglich nicht allgemein zu. Schreiben Sie vernünftige Gedankengänge. Wenn Sie eine Ausnahme auslösen möchten, wenn ein erforderliches Element fehlt : if (!sequence.Any(v => v == true)). Wenn Sie nur fortfahren möchten, wenn alles einer bestimmten Spezifikation entspricht : if (sequence.All(v => v < 10)).
Timo

Antworten:

344

Implementierung von Allgemäß ILSpy (wie in Ich habe tatsächlich nachgesehen und nicht nach "Nun, diese Methode funktioniert ein bisschen wie ...", was ich tun könnte, wenn wir eher die Theorie als die Auswirkungen diskutieren würden).

public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    if (predicate == null)
    {
        throw Error.ArgumentNull("predicate");
    }
    foreach (TSource current in source)
    {
        if (!predicate(current))
        {
            return false;
        }
    }
    return true;
}

Implementierung von Anynach ILSpy:

public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
    if (source == null)
    {
        throw Error.ArgumentNull("source");
    }
    if (predicate == null)
    {
        throw Error.ArgumentNull("predicate");
    }
    foreach (TSource current in source)
    {
        if (predicate(current))
        {
            return true;
        }
    }
    return false;
}

Natürlich könnte es einen subtilen Unterschied in der produzierten IL geben. Aber nein, nein gibt es nicht. Die IL ist ziemlich gleich, aber für die offensichtliche Umkehrung der Rückgabe von true bei Prädikatübereinstimmung gegenüber der Rückgabe von false bei Prädikatfehlanpassung.

Dies ist natürlich nur Linq-for-Objects. Es ist möglich, dass ein anderer Linq-Anbieter einen viel besser behandelt als den anderen, aber wenn dies der Fall ist, ist es ziemlich zufällig, welcher die optimalere Implementierung erhalten hat.

Es scheint, dass die Regel nur auf jemanden zurückzuführen ist, der das Gefühl hat, if(determineSomethingTrue)einfacher und lesbarer zu sein alsif(!determineSomethingFalse) . Und fairerweise denke ich, dass sie einen gewissen Punkt haben, den ich oft if(!someTest)verwirrend finde *, wenn es einen alternativen Test von gleicher Ausführlichkeit und Komplexität gibt, der für die Bedingung, auf die wir reagieren wollen, wahr ist. Ich persönlich finde jedoch nichts, was eine der beiden von Ihnen angegebenen Alternativen gegenüber der anderen bevorzugt, und würde mich vielleicht ganz leicht zu der ersteren neigen, wenn das Prädikat komplizierter wäre.

* Nicht verwirrend wie in Ich verstehe nicht, aber verwirrend wie in Ich mache mir Sorgen, dass es einen subtilen Grund für die Entscheidung gibt, den ich nicht verstehe, und es braucht ein paar mentale Sprünge, um zu erkennen, dass "Nein, sie haben sich einfach dazu entschlossen Warten Sie auf diese Weise, worauf habe ich diesen Code noch einmal angeschaut? ... "

Jon Hanna
quelle
8
Ich bin mir nicht sicher, was hinter den Kulissen gemacht wird, aber für mich ist viel besser lesbar: wenn (nicht welche) als wenn (alle nicht gleich).
VikciaR
49
Es gibt einen großen Unterschied, wenn Ihre Aufzählung keine Werte hat. 'Any' würde immer FALSE zurückgeben, und 'All' würde immer TRUE zurückgeben. Zu sagen, dass eins das logische Äquivalent des anderen ist, ist also nicht ganz richtig!
Arnaud
44
@Arnaud Anywird zurückkehren falseund daher !Anyzurückkehren true, so dass sie identisch sind.
Jon Hanna
11
@Arnaud Es gibt keine Person, die kommentiert hat, dass Any and All logisch gleichwertig sind. Oder anders ausgedrückt: Alle Personen, die Kommentare abgegeben haben, haben nicht gesagt, dass Any und All logisch gleichwertig sind. Die Äquivalenz liegt zwischen! Any (Prädikat) und All (! Prädikat).
Jim Balter
7
@MacsDickinson, das ist überhaupt kein Unterschied, weil Sie keine entgegengesetzten Prädikate vergleichen. Das Äquivalent zu !test.Any(x => x.Key == 3 && x.Value == 1)diesen Verwendungen Allist test.All(x => !(x.Key == 3 && x.Value == 1))(was in der Tat äquivalent zu ist test.All(x => x.Key != 3 || x.Value != 1)).
Jon Hanna
55

Diese Erweiterungsmethoden machen Ihren Code möglicherweise besser lesbar:

public static bool None<TSource>(this IEnumerable<TSource> source)
{
    return !source.Any();
}

public static bool None<TSource>(this IEnumerable<TSource> source, 
                                 Func<TSource, bool> predicate)
{
    return !source.Any(predicate);
}

Jetzt anstelle Ihres Originals

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

du könntest sagen

if (acceptedValues.None(v => v == someValue))
{
    // exception logic
}
AakashM
quelle
6
Danke - ich habe bereits darüber nachgedacht, diese in unserer Commons-Bibliothek zu implementieren, aber ich habe noch nicht entschieden, ob es eine gute Idee ist. Ich bin damit einverstanden, dass sie den Code lesbarer machen, aber ich bin besorgt, dass sie keinen ausreichenden Mehrwert bieten.
Mark
2
Ich habe nach None gesucht und es nicht gefunden. Es ist sehr viel besser lesbar.
Rhyous
Ich musste Nullprüfungen hinzufügen: return source == null || ! source.Any (Prädikat);
Rhyous
27

Beide haben die gleiche Leistung, da beide die Aufzählung beenden, nachdem das Ergebnis bestimmt werden kann - Any()beim ersten Element wird das übergebene Prädikat ausgewertet trueund All()beim ersten Element wird das Prädikat ausgewertet false.

Glassplitter
quelle
21

All Kurzschlüsse bei der ersten Nichtübereinstimmung, daher ist dies kein Problem.

Ein Bereich der Subtilität ist das

 bool allEven = Enumerable.Empty<int>().All(i => i % 2 == 0); 

Ist wahr. Alle Elemente in der Sequenz sind gerade.

Weitere Informationen zu dieser Methode finden Sie in der Dokumentation zu Enumerable.All .

Anthony Pegram
quelle
12
Ja, aber bool allEven = !Enumerable.Empty<int>().Any(i => i % 2 != 0)das stimmt auch.
Jon Hanna
1
@ Jon semantisch keine! = Alle. Semantisch gesehen haben Sie entweder keine oder alle, aber im Fall von .All () ist keine nur eine Teilmenge aller Sammlungen, die für alle true zurückgeben, und diese Diskrepanz kann zu Fehlern führen, wenn Sie sich dessen nicht bewusst sind. +1 für diesen Anthony
Rune FS
@ RunFS Ich folge nicht. Semantisch und logisch ist "keiner, wo es nicht wahr ist, dass ..." in der Tat dasselbe wie "alles, wo es wahr ist, dass". ZB "Wo ist keines der akzeptierten Projekte von unserer Firma?" wird immer die gleiche Antwort haben wie "wo alle akzeptierten Projekte von anderen Unternehmen?" ...
Jon Hanna
... Nun, es ist wahr, dass Sie Fehler haben können, wenn Sie annehmen, dass "alle Elemente sind ..." bedeutet, dass es mindestens ein Element gibt, das mindestens ein Element ist, das den Test erfüllt, da "alle Elemente ..." "gilt immer für das leere Set, das bestreite ich überhaupt nicht. Ich fügte jedoch hinzu, dass das gleiche Problem auftreten kann, wenn angenommen wird, dass "keines der Elemente ..." bedeutet, dass mindestens ein Element den Test nicht erfüllt, da "keines der Elemente ..." auch immer für den leeren Satz gilt . Es ist nicht so, dass ich mit Anthonys Argument nicht einverstanden bin, sondern dass es meiner Meinung nach auch für das andere der beiden zur Diskussion stehenden Konstrukte gilt.
Jon Hanna
@ Jon du redest Logik und ich spreche Linguistik. Das menschliche Gehirn kann ein Negativ nicht verarbeiten (bevor es das Positive verarbeitet, an welchem ​​Punkt es es dann negieren kann), so dass es in diesem Sinne einen ziemlichen Unterschied zwischen den beiden gibt. Das macht die von Ihnen vorgeschlagene Logik nicht falsch
Rune FS
8

All()bestimmt, ob alle Elemente einer Sequenz eine Bedingung erfüllen.
Any()bestimmt, ob ein Element einer Sequenz die Bedingung erfüllt.

var numbers = new[]{1,2,3};

numbers.All(n => n % 2 == 0); // returns false
numbers.Any(n => n % 2 == 0); // returns true
emy
quelle
7

Nach diesem Link

Beliebig - Überprüft mindestens ein Spiel

Alle - Überprüft, ob alle übereinstimmen

rcarvalhoxavier
quelle
1
Sie haben Recht, aber sie hören gleichzeitig für eine bestimmte Sammlung auf. Alle Pausen, wenn die Bedingung fehlschlägt, und alle Pausen, wenn sie Ihrem Prädikat entsprechen. Also technisch nicht anders als szenisch
WPFKK
6

Wie andere Antworten bereits ausführlich dargelegt haben: Hier geht es nicht um Leistung, sondern um Klarheit.

Es gibt breite Unterstützung für beide Optionen:

if (!acceptedValues.Any(v => v == someValue))
{
    // exception logic
}

if (acceptedValues.All(v => v != someValue))
{
    // exception logic
}

Aber ich denke, dies könnte eine breitere Unterstützung erreichen :

var isValueAccepted = acceptedValues.Any(v => v == someValue);
if (!isValueAccepted)
{
    // exception logic
}

Einfach den Booleschen Wert zu berechnen (und ihn zu benennen), bevor etwas negiert wird, macht dies in meinem Kopf sehr deutlich.

Michael Haren
quelle
3

Wenn Sie sich die Enumerable-Quelle ansehen, werden Sie feststellen, dass die Implementierung von Anyund Allziemlich nah ist:

public static bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
    if (source == null) throw Error.ArgumentNull("source");
    if (predicate == null) throw Error.ArgumentNull("predicate");
    foreach (TSource element in source) {
        if (predicate(element)) return true;
    }
    return false;
}

public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
    if (source == null) throw Error.ArgumentNull("source");
    if (predicate == null) throw Error.ArgumentNull("predicate");
    foreach (TSource element in source) {
        if (!predicate(element)) return false;
    }
    return true;
}

Es gibt keine Möglichkeit, dass eine Methode wesentlich schneller als die andere ist, da der einzige Unterschied in einer booleschen Negation liegt. Ziehen Sie daher die Lesbarkeit dem falschen Leistungsgewinn vor.

Thomas Ayoub
quelle