Überprüfen Sie die foreach-Schleife auf Null

91

Gibt es eine bessere Möglichkeit, Folgendes zu tun:
Ich muss prüfen, ob in der Datei null vorkommt. Kopfzeilen, bevor ich mit der Schleife fortfahre

if (file.Headers != null)
{
  foreach (var h in file.Headers)
  {
   //set lots of properties & some other stuff
  }
}

Kurz gesagt, es sieht ein bisschen hässlich aus, das foreach in das if zu schreiben, da in meinem Code ein Einrückungsgrad auftritt.

Ist etwas, das zu bewerten wäre

foreach(var h in (file.Headers != null))
{
  //do stuff
}

möglich?

Eminem
quelle
3
Sie können einen Blick hier werfen: stackoverflow.com/questions/6937407/…
Adrian Fâciu
stackoverflow.com/questions/872323/… ist eine andere Idee.
Weismat
1
@AdrianFaciu Ich denke das ist ganz anders. Die Frage prüft zuerst, ob die Sammlung null ist, bevor die for-each ausgeführt wird. Ihr Link prüft, ob das Element in der Sammlung null ist.
Rikitikitik
1
C # 8 könnte einfach eine nullbedingte foreach haben, dh eine Syntax wie diese: foreach? (var i in collection) {} Ich denke, es ist ein Szenario, das häufig genug ist, um dies zu rechtfertigen, und angesichts der jüngsten nullbedingten Ergänzungen der Sprache, für die es hier Sinn macht?
MMS

Antworten:

120

Nur als kleine kosmetische Ergänzung zu Runes Vorschlag können Sie Ihre eigene Erweiterungsmethode erstellen:

public static IEnumerable<T> OrEmptyIfNull<T>(this IEnumerable<T> source)
{
    return source ?? Enumerable.Empty<T>();
}

Dann können Sie schreiben:

foreach (var header in file.Headers.OrEmptyIfNull())
{
}

Ändern Sie den Namen nach Geschmack :)

Jon Skeet
quelle
75

Angenommen, der Elementtyp in file.Headers ist T, könnten Sie dies tun

foreach(var header in file.Headers ?? Enumerable.Empty<T>()){
  //do stuff
}

Dadurch wird eine leere Aufzählung von T erstellt, wenn file.Headers null ist. Wenn der Dateityp ein Typ ist, den Sie besitzen, würde ich jedoch in Betracht ziehen, Headersstattdessen den Getter von zu ändern .nullist der Wert von unknown. Wenn möglich, anstatt null als "Ich weiß, dass es keine Elemente gibt" zu verwenden, wenn null tatsächlich (/ ursprünglich) als "Ich weiß nicht, ob es Elemente gibt" interpretiert werden sollte, verwenden Sie eine leere Menge, um anzuzeigen dass Sie wissen, dass das Set keine Elemente enthält. Das wäre auch DRY'er, da Sie die Nullprüfung nicht so oft durchführen müssen.

BEARBEITEN Als Folge von Jons Vorschlag können Sie auch eine Erweiterungsmethode erstellen, die den obigen Code in ändert

foreach(var header in file.Headers.OrEmptyIfNull()){
  //do stuff
}

In dem Fall, in dem Sie den Getter nicht ändern können, wäre dies meine eigene Präferenz, da es die Absicht klarer ausdrückt, indem Sie der Operation einen Namen geben (OrEmptyIfNull).

Die oben erwähnte Erweiterungsmethode kann es dem Optimierer unmöglich machen, bestimmte Optimierungen zu erkennen. Insbesondere diejenigen, die mit IList in Verbindung stehen, indem diese Methode überladen wird, können eliminiert werden

public static IList<T> OrEmptyIfNull<T>(this IList<T> source)
{
    return source ?? Array.Empty<T>();
}
Rune FS
quelle
@ kjbartels Antwort (unter "stackoverflow.com/a/32134295/401246" ist die beste Lösung, da sie nicht: a) eine Leistungsverschlechterung der (auch wenn nicht null) Entartung der gesamten Schleife zum LCD von IEnumerable<T>(wie bei Verwendung von ??) beinhaltet. würde), b) das Hinzufügen einer Erweiterungsmethode zu jedem Projekt erfordern, oder c) das Vermeiden von null IEnumerables(Pffft! Puh-LEAZE! SMH. ) erfordern (cuz nullbedeutet N / A, während leere Liste bedeutet, dass es anwendbar ist, aber derzeit ist, gut, leer!, dh ein Mitarbeiter könnte Provisionen haben, die N / A für Nicht-Verkäufe oder leer für Verkäufe sind, wenn er keine verdient hat).
Tom
@ Tom Abgesehen von einer Nullprüfung gibt es keine Strafe für die Fälle, in denen der Enumerator nicht null ist. Es ist unmöglich, diese Überprüfung zu vermeiden und gleichzeitig sicherzustellen, dass die Aufzählung nicht null ist. Der obige Code erfordert, dass die Header einen Wert haben, IEnumerable der restriktiver als die foreachAnforderungen, aber weniger restriktiv als die Anforderungen List<T>in der Antwort ist, auf die Sie verlinken. Welche haben die gleiche Leistungseinbuße beim Testen, ob die Aufzählung null ist.
Rune FS
Ich habe das "LCD" -Problem auf den Kommentar von Eric Lippert zu Vlad Bezdens Antwort im selben Thread wie kjbartels Antwort gestützt: "@CodeInChaos: Ah, ich verstehe Ihren Standpunkt jetzt. Wenn der Compiler erkennen kann, dass das" foreach "iteriert über eine Liste <T> oder ein Array kann es dann die foreach optimieren, um Aufzähler vom Werttyp zu verwenden oder tatsächlich eine "for" -Schleife zu generieren. Wenn es gezwungen ist, entweder über eine Liste oder die leere Sequenz aufzulisten, muss es zurück zur " kleinster gemeinsamer Nenner "Codegen, der in einigen Fällen langsamer sein und mehr Speicherdruck erzeugen kann ....". Stimmen Sie zu, es erfordert List<T>.
Tom
@tom Die Antwort setzt voraus, dass file.Headers ein IEnumerable <T> ist. In diesem Fall kann der Compiler die Optimierung nicht durchführen. Es ist jedoch ziemlich einfach, die Lösung der Erweiterungsmethode zu erweitern, um dies zu vermeiden. Siehe bearbeiten
Rune FS
19

Ehrlich gesagt rate ich: saugen Sie einfach den nullTest auf. Ein nullTest ist nur ein brfalseoder brfalse.s; alles andere wird sich viel mehr Arbeit (Tests, Aufgaben, zusätzliche Methodenaufrufe, unnötige beinhalten GetEnumerator(), MoveNext(), Dispose()auf dem Iterator, etc).

Ein ifTest ist einfach, offensichtlich und effizient.

Marc Gravell
quelle
1
Sie machen einen interessanten Punkt, Marc, aber ich versuche derzeit, die Einrückungsstufen des Codes zu verringern. Aber ich werde Ihren Kommentar im Hinterkopf behalten, wenn ich die Leistung zur Kenntnis nehmen muss.
Eminem
3
Nur eine kurze Anmerkung zu diesem Marc. Jahre nachdem ich diese Frage gestellt hatte und einige Leistungsverbesserungen implementieren musste, war Ihr Rat äußerst nützlich. Vielen Dank
Eminem
12

Das "Wenn" vor der Iteration ist in Ordnung, nur wenige dieser "hübschen" Semantiken können Ihren Code weniger lesbar machen.

Wenn die Einrückung Sie stört, können Sie das zu ändernde if ändern:

if(file.Headers == null)  
   return;

und Sie gelangen nur dann zur foreach-Schleife, wenn die Eigenschaft headers einen wahren Wert enthält.

Eine andere Option, über die ich nachdenken kann, ist die Verwendung des Null-Koaleszenz-Operators in Ihrer foreach-Schleife und die vollständige Vermeidung der Nullprüfung. Stichprobe:

List<int> collection = new List<int>();
collection = null;
foreach (var i in collection ?? Enumerable.Empty<int>())
{
    //your code here
}

(Ersetzen Sie die Sammlung durch Ihr wahres Objekt / Ihren wahren Typ)

Tamir
quelle
Ihre erste Option funktioniert nicht, wenn sich außerhalb der if-Anweisung Code befindet.
Rikitikitik
Ich bin damit einverstanden, dass die if-Anweisung im Vergleich zum Erstellen einer neuen Liste einfach und kostengünstig zu implementieren ist, nur zur Verschönerung des Codes.
Andreas Johansson
9

Verwenden des nullbedingten Operators und von ForEach (), die schneller als die Standard-foreach-Schleife arbeiten.
Sie müssen die Sammlung jedoch in List umwandeln.

   listOfItems?.ForEach(item => // ... );
Andrei Karcheuski
quelle
4
Bitte fügen Sie einige Erklärungen zu Ihrer Antwort hinzu und geben Sie explizit an, warum diese Lösung funktioniert, und nicht nur einen Code-
Einzeiler
3

Ich verwende eine nette kleine Erweiterungsmethode für diese Szenarien:

  public static class Extensions
  {
    public static IList<T> EnsureNotNull<T>(this IList<T> list)
    {
      return list ?? new List<T>();
    }
  }

Da es sich bei den Headern um eine Typliste handelt, können Sie Folgendes tun:

foreach(var h in (file.Headers.EnsureNotNull()))
{
  //do stuff
}
Wolfgang Ziegler
quelle
1
Sie können den ??Operator verwenden und die return-Anweisung aufreturn list ?? new List<T>;
Rune FS
1
@wolfgangziegler, Wenn ich richtig verstehe, wird der Test für nullin Ihrer Probe file.Headers.EnsureNotNull() != nullnicht benötigt und ist sogar falsch?
Remko Jansen
0

In einigen Fällen würde ich eine etwas andere generische Variante bevorzugen, vorausgesetzt, dass Standardauflistungskonstruktoren in der Regel leere Instanzen zurückgeben.

Es wäre besser, diese Methode zu benennen NewIfDefault. Dies kann nicht nur für Sammlungen nützlich sein, daher IEnumerable<T>ist die Typbeschränkung möglicherweise redundant.

public static TCollection EmptyIfDefault<TCollection, T>(this TCollection collection)
        where TCollection: class, IEnumerable<T>, new()
    {
        return collection ?? new TCollection();
    }
N. Kudryavtsev
quelle