Ist if (items! = Null) vor foreach überflüssig (T item in items)?

103

Ich stoße oft auf folgenden Code:

if ( items != null)
{
   foreach(T item in items)
   {
        //...
   }
}

Grundsätzlich stellt die ifBedingung sicher, dass der foreachBlock nur ausgeführt wird, wenn er itemsnicht null ist. Ich frage mich, ob die ifBedingung wirklich benötigt wird oder foreachob der Fall behandelt wird, wenn items == null.

Ich meine, kann ich einfach schreiben

foreach(T item in items)
{
    //...
}

ohne sich Gedanken darüber zu machen, ob itemsnull ist oder nicht? Ist der ifZustand überflüssig? Oder dies hängt von der Art der itemsoder vielleicht auf Tso gut?

Nawaz
quelle
1
@ kjbartel Antwort (bei „ stackoverflow.com/a/32134295/401246 “ ist die beste Lösung, weil es nicht der Fall ist: a) eine Leistungsverschlechterung von (auch wenn sie nicht beteiligt null) verallgemeinern die gesamte Schleife zum LCD Enumerable(wie die Verwendung ??würde ), b) erfordern, dass jedem Projekt eine Erweiterungsmethode hinzugefügt wird, oder c) dass zunächst null IEnumerables (Pffft! Puh-LEAZE! SMH.) vermieden wird (cuz nullbedeutet N / A, während leere Liste bedeutet, dass es anwendbar ist, aber ist Derzeit, na ja , leer !, dh ein Mitarbeiter könnte Provisionen haben, die N / A für Nicht-Verkäufe oder leer für Verkäufe sind, wenn sie keine verdient haben.
Tom

Antworten:

115

Sie müssen noch prüfen, ob (items! = Null), sonst erhalten Sie die NullReferenceException. Sie können jedoch Folgendes tun:

List<string> items = null;  
foreach (var item in items ?? new List<string>())
{
    item.Dump();
}

Sie können jedoch die Leistung überprüfen. Also bevorzuge ich es immer noch, wenn (items! = Null) zuerst zu haben.

Aufgrund von Erics Lippert-Vorschlag habe ich den Code geändert in:

List<string> items = null;  
foreach (var item in items ?? Enumerable.Empty<string>())
{
    item.Dump();
}
Vlad Bezden
quelle
31
Nette Idee; Ein leeres Array wäre vorzuziehen, da es weniger Speicher verbraucht und weniger Speicherdruck erzeugt. Enumerable.Empty <string> wäre noch vorzuziehen, da das leere Array, das es generiert, zwischengespeichert und wiederverwendet wird.
Eric Lippert
5
Ich erwarte, dass der zweite Code langsamer ist. Es degeneriert die Sequenz zu einer, IEnumerable<T>die sich wiederum zu einem Enumerator zu einer Schnittstelle verschlechtert, wodurch die Iteration langsamer wird. Mein Test zeigte eine Verschlechterung des Faktors 5 für die Iteration über ein int-Array.
CodesInChaos
11
@CodeInChaos: Finden Sie normalerweise, dass die Geschwindigkeit der Aufzählung einer leeren Sequenz der Leistungsengpass in Ihrem Programm ist?
Eric Lippert
14
Es reduziert nicht nur die Aufzählungsgeschwindigkeit der leeren Sequenz, sondern auch der gesamten Sequenz. Und wenn die Sequenz lang genug ist, kann es wichtig sein. Für den meisten Code sollten wir den idiomatischen Code wählen. Die beiden von Ihnen genannten Zuordnungen sind jedoch in noch weniger Fällen ein Leistungsproblem.
CodesInChaos
15
@ CodeInChaos: Ah, ich verstehe deinen Standpunkt jetzt. Wenn der Compiler erkennt, dass "foreach" über eine Liste <T> oder ein Array iteriert, kann er foreach optimieren, um Aufzähler vom Werttyp zu verwenden oder tatsächlich eine "for" -Schleife zu generieren. Wenn eine Liste oder die leere Sequenz aufgezählt werden muss, muss zum Codegen "kleinster gemeinsamer Nenner" zurückgekehrt werden, der in einigen Fällen langsamer sein und mehr Speicherdruck erzeugen kann. Dies ist ein subtiler, aber ausgezeichneter Punkt. Natürlich ist die Moral der Geschichte - wie immer - wenn Sie ein Perf-Problem haben, dann profilieren Sie es, um herauszufinden, was der wahre Engpass ist.
Eric Lippert
68

Mit C # 6 können Sie den neuen bedingten Nulloperator zusammen mit List<T>.ForEach(Action<T>)(oder Ihrer eigenen IEnumerable<T>.ForEachErweiterungsmethode) verwenden.

List<string> items = null;
items?.ForEach(item =>
{
    // ...
});
kjbartel
quelle
Elegante Antwort. Vielen Dank!
Steve
2
Dies ist die beste Lösung, da sie nicht: a) eine Leistungsverschlechterung der (auch wenn nicht null) Verallgemeinerung der gesamten Schleife auf das LCD von Enumerable(wie bei Verwendung ??) beinhaltet, b) das Hinzufügen einer Erweiterungsmethode zu jedem Projekt erfordert, oder c ) erfordern zunächst das Vermeiden von null IEnumerables (Pffft! Puh-LEAZE! SMH.) (cuz nullbedeutet N / A, während leere Liste bedeutet, dass es anwendbar ist, aber derzeit gut leer ist !, dh ein Empl. könnte Provisionen haben, die N / A für Nicht-Verkäufe oder leer für Verkäufe, wenn sie keine verdient haben).
Tom
6
@ Tom: Es wird davon ausgegangen, dass dies itemseher ein Gedanke List<T>als irgendein ist IEnumerable<T>. (Oder haben Sie eine benutzerdefinierte Erweiterungsmethode, von der Sie gesagt haben, dass Sie nicht möchten, dass es sie gibt ...) Außerdem würde ich sagen, dass es sich nicht lohnt, 11 Kommentare hinzuzufügen, die alle im Grunde sagen, dass Ihnen eine bestimmte Antwort gefällt.
Jon Skeet
2
@ Tom: Ich würde Sie dringend davon abhalten, dies in Zukunft zu tun. Stellen Sie sich vor, jeder, der mit Ihrem Kommentar nicht einverstanden ist, fügt seine Kommentare allen Ihren hinzu . (Stellen Sie sich vor, ich hätte meine Antwort hier nur elf Mal geschrieben.) Dies ist einfach keine produktive Verwendung von Stack Overflow.
Jon Skeet
1
Ich würde auch annehmen, dass es einen Leistungseinbruch geben würde, der den Delegierten gegenüber einem Standard anruft foreach. Besonders für eine Liste, die meiner Meinung nach in eine forSchleife umgewandelt wird.
Kjbartel
37

Das eigentliche Mitnehmen hier sollte eine Sequenz sein, die fast nie null sein sollte . Machen Sie es einfach in allen Ihren Programmen zu einer Invariante, dass eine Sequenz niemals null ist, wenn Sie sie haben. Es wird immer als leere Sequenz oder eine andere echte Sequenz initialisiert.

Wenn eine Sequenz niemals null ist, müssen Sie sie natürlich nicht überprüfen.

Eric Lippert
quelle
1
Wie wäre es, wenn Sie die Sequenz von einem WCF-Dienst erhalten? Es könnte null sein, oder?
Nawaz
4
@Nawaz: Wenn ich einen WCF-Dienst hätte, der mir Nullsequenzen zurückgibt, die beabsichtigen, dass sie leere Sequenzen sind, würde ich das als Fehler melden. Das heißt: Wenn Sie sich mit schlecht geformten Ausgaben von wohl fehlerhaften Diensten befassen müssen, dann müssen Sie sich damit befassen, indem Sie nach Null suchen.
Eric Lippert
7
Es sei denn natürlich, null und leer bedeuten völlig unterschiedliche Dinge. Manchmal gilt das für Sequenzen.
Konfigurator
@Nawaz Wie wäre es mit DataTable.Rows, die anstelle einer leeren Sammlung null zurückgeben. Vielleicht ist das ein Fehler?
Neil B
@ kjbartel Antwort (bei „ stackoverflow.com/a/32134295/401246 “ ist die beste Lösung, weil es nicht der Fall ist: a) eine Leistungsverschlechterung von (auch wenn sie nicht beteiligt null) verallgemeinern die gesamte Schleife zum LCD Enumerable(wie die Verwendung ??würde ), b) erfordern, dass jedem Projekt eine Erweiterungsmethode hinzugefügt wird, oder c) dass zunächst null IEnumerables (Pffft! Puh-LEAZE! SMH.) vermieden wird (cuz nullbedeutet N / A, während leere Liste bedeutet, dass es anwendbar ist, aber ist Derzeit, na ja , leer !, dh ein Mitarbeiter könnte Provisionen haben, die N / A für Nicht-Verkäufe oder leer für Verkäufe sind, wenn sie keine verdient haben.
Tom
10

Tatsächlich gibt es auf diesem @Connect eine Funktionsanforderung: http://connect.microsoft.com/VisualStudio/feedback/details/93497/foreach-should-check-for-null

Und die Antwort ist ganz logisch:

Ich denke, dass die meisten foreach-Schleifen mit der Absicht geschrieben wurden, eine Nicht-Null-Sammlung zu iterieren. Wenn Sie versuchen, null zu durchlaufen, sollten Sie Ihre Ausnahme erhalten, damit Sie Ihren Code reparieren können.

Teoman Soygul
quelle
Ich denke, es gibt Vor- und Nachteile dafür, also haben sie beschlossen, es so zu belassen, wie es ursprünglich entworfen wurde. Immerhin ist der Foreach nur ein syntaktischer Zucker. Wenn Sie items.GetEnumerator () aufgerufen hätten, wäre dies auch abgestürzt, wenn items null gewesen wäre. Sie mussten dies also zuerst testen.
Marius Bancila
6

Sie könnten es immer mit einer Nullliste testen ... aber das habe ich auf der msdn-Website gefunden

foreach-statement:
    foreach   (   type   identifier   in   expression   )   embedded-statement 

Wenn expression den Wert null hat, wird eine System.NullReferenceException ausgelöst.

nbz
quelle
2

Es ist nicht überflüssig. Zur Laufzeit werden Elemente in eine IEnumerable umgewandelt und die GetEnumerator-Methode aufgerufen. Dies führt zu einer Dereferenzierung von Elementen, die fehlschlagen

Boca
quelle
1
1) Die Sequenz wird nicht unbedingt besetzt IEnumerableund 2) Es ist eine Entwurfsentscheidung, sie werfen zu lassen. C # könnte diese nullPrüfung leicht einfügen, wenn die Entwickler dies für eine gute Idee halten.
CodesInChaos
2

Sie können die Nullprüfung in eine Erweiterungsmethode einkapseln und ein Lambda verwenden:

public static class EnumerableExtensions {
  public static void ForEach<T>(this IEnumerable<T> self, Action<T> action) {
    if (self != null) {
      foreach (var element in self) {
        action(element);
      }
    }
  }
}

Der Code wird:

items.ForEach(item => { 
  ...
});

If kann noch präziser sein, wenn Sie nur eine Methode aufrufen möchten, die ein Element nimmt und zurückgibt void:

items.ForEach(MethodThatTakesAnItem);
Jordão
quelle
1

Du brauchst das. Beim foreachZugriff auf den Container wird eine Ausnahme angezeigt, um die Iteration anderweitig einzurichten.

Unter den Abdeckungen, foreachverwendet eine Schnittstelle für die Sammlung Klasse implementiert , um die Iteration durchzuführen. Die generische äquivalente Schnittstelle ist hier .

Die foreach-Anweisung der C # -Sprache (für jede in Visual Basic) verbirgt die Komplexität der Enumeratoren. Daher wird empfohlen, foreach zu verwenden, anstatt den Enumerator direkt zu manipulieren.

Steve Townsend
quelle
1
Nur als Hinweis, dass die Schnittstelle technisch nicht verwendet wird, wird die Ententypisierung verwendet: blogs.msdn.com/b/kcwalina/archive/2007/07/18/ducknotation.aspx Die Schnittstellen stellen sicher, dass die richtigen Methoden und Eigenschaften vorhanden sind aber und helfen, die Absicht zu verstehen. sowie außerhalb foreach verwenden ...
ShuggyCoUk
0

Der Test ist erforderlich, denn wenn die Auflistung null ist, löst foreach eine NullReferenceException aus. Es ist eigentlich ganz einfach, es auszuprobieren.

List<string> items = null;
foreach(var item in items)
{
   Console.WriteLine(item);
}
Marius Bancila
quelle
0

Der zweite wird ein NullReferenceExceptionmit der Nachricht werfenObject reference not set to an instance of an object.

Harryovers
quelle
0

Wie hier erwähnt , müssen Sie überprüfen, ob es nicht null ist.

Verwenden Sie keinen Ausdruck, der null ergibt.

Renatas M.
quelle
0

In C # 6 können Sie etw wie folgt schreiben:

// some string from file or UI, i.e.:
// a) string s = "Hello, World!";
// b) string s = "";
// ...
var items = s?.Split(new char[] { ',', '!', ' ' }) ?? Enumerable.Empty<string>();  
foreach (var item in items)
{
    //..
}

Es ist im Grunde Vlad Bezdens Lösung, aber mit dem ?? Ausdruck, um immer ein Array zu generieren, das nicht null ist und daher die foreach überlebt, anstatt diese Prüfung in der foreach-Klammer zu haben.

DR. RAI
quelle