it-swarm.com.de

Filtern von foreach-Schleifen mit einer where-Bedingung im Vergleich zu continue-Schutzklauseln

Ich habe einige Programmierer gesehen, die dies verwenden:

foreach (var item in items)
{
    if (item.Field != null)
        continue;

    if (item.State != ItemStates.Deleted)
        continue;

    // code
}

anstatt wo ich normalerweise verwenden würde:

foreach (var item in items.Where(i => i.Field != null && i.State != ItemStates.Deleted))
{
    // code
}

Ich habe sogar eine Kombination von beiden gesehen. Ich mag die Lesbarkeit mit 'Weiter' sehr, besonders bei komplexeren Bedingungen. Gibt es überhaupt einen Leistungsunterschied? Bei einer Datenbankabfrage gehe ich davon aus, dass es eine geben würde. Was ist mit regulären Listen?

24
Paprik

Ich würde dies als geeigneten Ort für die Verwendung von Befehls-/Abfragetrennung betrachten. Zum Beispiel:

// query
var validItems = items.Where(i => i.Field != null && i.State != ItemStates.Deleted);
// command
foreach (var item in validItems) {
    // do stuff
}

Auf diese Weise können Sie dem Abfrageergebnis auch einen guten selbstdokumentierenden Namen geben. Es hilft Ihnen auch dabei, Möglichkeiten für das Refactoring zu erkennen, da es viel einfacher ist, Code zu refactorisieren, der nur Daten abfragt oder nur mutiert, als gemischten Code, der versucht, beides zu tun.

Beim Debuggen können Sie vor foreach eine Pause einlegen, um schnell zu überprüfen, ob der Inhalt von validItems den Erwartungen entspricht. Sie müssen nicht in das Lambda steigen, es sei denn, Sie müssen. Wenn Sie in das Lambda einsteigen müssen, schlage ich vor, es in eine separate Funktion zu zerlegen und stattdessen durchzugehen.

Gibt es einen Leistungsunterschied? Wenn die Abfrage von einer Datenbank unterstützt wird, kann die LINQ-Version schneller ausgeführt werden, da die SQL-Abfrage ) kann effizienter sein. Wenn es sich um LINQ to Objects handelt, werden Sie keinen wirklichen Leistungsunterschied feststellen. Profilieren Sie wie immer Ihren Code und beheben Sie die tatsächlich gemeldeten Engpässe, anstatt zu versuchen, Optimierungen im Voraus vorherzusagen.

65

Natürlich gibt es einen Leistungsunterschied. .Where() führt dazu, dass für jedes einzelne Element ein Delegatenaufruf ausgeführt wird. Ich würde mir jedoch überhaupt keine Sorgen um die Leistung machen:

  • Die beim Aufrufen eines Delegaten verwendeten Taktzyklen sind im Vergleich zu den Taktzyklen, die vom Rest des Codes verwendet werden, der die Sammlung durchläuft und die Bedingungen überprüft, vernachlässigbar.

  • Die Leistungsbeeinträchtigung beim Aufrufen eines Delegaten liegt in der Größenordnung einiger Taktzyklen, und zum Glück sind wir längst über die Tage hinaus, an denen wir uns um einzelne Taktzyklen kümmern mussten.

Wenn aus irgendeinem Grund die Leistung wirklich für Sie auf Taktzyklusebene wichtig ist, verwenden Sie List<Item> Anstelle von IList<Item>, Damit der Compiler direct (verwenden kann) und inlinable) Aufrufe anstelle von virtuellen Aufrufen, sodass der Iterator von List<T>, der eigentlich ein struct ist, nicht eingerahmt werden muss. Aber das ist wirklich unbedeutendes Zeug.

Eine Datenbankabfrage ist eine andere Situation, da (zumindest theoretisch) die Möglichkeit besteht, den Filter an das RDBMS zu senden, wodurch die Leistung erheblich verbessert wird: Nur übereinstimmende Zeilen führen die Reise vom RDBMS zu Ihrem Programm. Aber dafür denke ich, dass Sie linq verwenden müssten, ich glaube nicht, dass dieser Ausdruck so wie er ist an das RDBMS gesendet werden könnte.

Sie werden die Vorteile von if(x) continue; sofort sehen, wenn Sie diesen Code debuggen müssen: Ein Schritt über if()s und continues funktioniert gut; Ein einziger Schritt in den Filterdelegierten ist ein Schmerz.

7
Mike Nakis