If (items! = Null) è superfluo prima di foreach (T item in items)?


103

Mi imbatto spesso in codice come il seguente:

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

Fondamentalmente, la ifcondizione garantisce che il foreachblocco itemsvenga eseguito solo se non è nullo. Mi chiedo se la ifcondizione sia davvero necessaria o foreachse gestirà il caso items == null.

Voglio dire, posso semplicemente scrivere

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

senza preoccuparsi se itemsè nullo o no? La ifcondizione è superflua? O questo dipende dal tipo di itemso forse su Tcome bene?



1
@ risposta di kjbartel (a " stackoverflow.com/a/32134295/401246 " è la soluzione migliore, perché non è così: a) comportano una riduzione delle prestazioni di (anche quando non null) generalizzare l'intero ciclo per il display LCD di Enumerable(come l'utilizzo ??farebbe ), b) richiedono l'aggiunta di un metodo di estensione a ogni progetto, oppure c) richiedono di evitare null IEnumerables (Pffft! Puh-LEAZE! SMH.) per iniziare con (cuz, nullsignifica N / A, mentre elenco vuoto significa, è appl. ma è attualmente, beh, vuoto !, cioè un dipendente potrebbe avere Commissioni N / A per le non vendite o vuote per le vendite quando non ne ha guadagnate).
Tom

Risposte:


115

Devi ancora controllare se (items! = Null) altrimenti otterrai NullReferenceException. Tuttavia puoi fare qualcosa del genere:

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

ma potresti controllarne le prestazioni. Quindi preferisco ancora avere if (items! = Null) prima.

Sulla base del suggerimento di Eric Lippert, ho cambiato il codice in:

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

31
Idea carina; un array vuoto sarebbe preferibile perché consuma meno memoria e produce meno pressione di memoria. Enumerable.Empty <string> sarebbe ancora più preferibile perché memorizza nella cache l'array vuoto che genera e lo riutilizza.
Eric Lippert

5
Mi aspetto che il secondo codice sia più lento. Fa degenerare la sequenza in una sequenza IEnumerable<T>che a sua volta degenera in enumeratore in un'interfaccia che rende l'iterazione più lenta. Il mio test ha mostrato una degradazione del fattore 5 per l'iterazione su un array int.
CodesInChaos

11
@CodeInChaos: Di solito trovi che la velocità di enumerare una sequenza vuota è il collo di bottiglia delle prestazioni nel tuo programma?
Eric Lippert

14
Non riduce solo la velocità di enumerazione della sequenza vuota, ma anche dell'intera sequenza. E se la sequenza è abbastanza lunga può avere importanza. Per la maggior parte del codice dovremmo scegliere il codice idiomatico. Ma le due allocazioni che hai menzionato rappresenteranno un problema di prestazioni in ancora meno casi.
CodesInChaos

15
@CodeInChaos: Ah, ora capisco il tuo punto. Quando il compilatore può rilevare che "foreach" sta iterando su List <T> o su un array, può ottimizzare foreach per utilizzare enumeratori di tipo valore o generare effettivamente un ciclo "for". Quando è costretto a enumerare su un elenco o su una sequenza vuota , deve tornare al codegen del "minimo comune denominatore", che in alcuni casi può essere più lento e produrre più pressione sulla memoria. Questo è un punto sottile ma eccellente. Ovviamente, la morale della storia è, come sempre, se hai un problema di prestazioni, profilalo per scoprire qual è il vero collo di bottiglia.
Eric Lippert

68

Utilizzando C # 6 è possibile utilizzare il nuovo operatore condizionale null insieme a List<T>.ForEach(Action<T>)(o il proprio IEnumerable<T>.ForEachmetodo di estensione).

List<string> items = null;
items?.ForEach(item =>
{
    // ...
});

Risposta elegante. Grazie!
Steve,

2
Questa è la soluzione migliore, perché non: a) comporta il degrado delle prestazioni (anche quando non null) generalizzando l'intero ciclo all'LCD di Enumerable(come ??farebbe), b) richiede l'aggiunta di un metodo di estensione a ogni progetto, o c ) richiedono di evitare null IEnumerables (Pffft! Puh-LEAZE! SMH.) per cominciare (perchéz, nullsignifica N / A, mentre elenco vuoto significa, è app. ma è attualmente, beh, vuoto !, cioè un Empl. potrebbe avere Commissioni che N / A per non vendite o vuoto per le vendite quando non ne hanno guadagnate).
Tom

6
@ Tom: Presume che itemssia un pensiero List<T>, piuttosto che uno qualsiasi IEnumerable<T>. (O hai un metodo di estensione personalizzato, che hai detto che non vuoi che ci sia ...) Inoltre, direi che non vale davvero la pena aggiungere 11 commenti tutti fondamentalmente dicendo che ti piace una risposta particolare.
Jon Skeet

2
@ Tom: ti sconsiglio vivamente di farlo in futuro. Immagina se tutti quelli che non sono d'accordo con il tuo commento aggiungessero i loro commenti a tutti i tuoi . (Immagina di aver scritto la mia risposta qui, ma 11 volte.) Questo semplicemente non è un uso produttivo di Stack Overflow.
Jon Skeet

1
Presumo anche che ci sarebbe un calo delle prestazioni chiamando il delegato rispetto a uno standard foreach. Soprattutto per un elenco che penso venga convertito in un forciclo.
kjbartel

37

La vera conclusione qui dovrebbe essere che una sequenza non dovrebbe quasi mai essere nulla in primo luogo . Rendi semplicemente invariante in tutti i tuoi programmi che se hai una sequenza, non è mai nulla. Viene sempre inizializzato per essere la sequenza vuota o qualche altra sequenza originale.

Se una sequenza non è mai nulla, ovviamente non è necessario controllarla.


1
Che ne dici se ottieni la sequenza da un servizio WCF? Potrebbe essere nullo, giusto?
Nawaz

4
@Nawaz: Se avessi un servizio WCF che mi ha restituito sequenze nulle intendendole essere sequenze vuote, allora lo segnalerei a loro come un bug. Detto questo: se hai a che fare con un output mal formato di servizi discutibilmente bacati, allora sì, devi affrontarlo verificando null.
Eric Lippert

7
A meno che, ovviamente, null e vuoto significhino cose completamente diverse. A volte è valido per le sequenze.
configuratore

@Nawaz Che ne dici di DataTable.Rows che restituisce null invece di una raccolta vuota. Forse è un bug?
Neil B

@ risposta di kjbartel (a " stackoverflow.com/a/32134295/401246 " è la soluzione migliore, perché non è così: a) comportano una riduzione delle prestazioni di (anche quando non null) generalizzare l'intero ciclo per il display LCD di Enumerable(come l'utilizzo ??farebbe ), b) richiedono l'aggiunta di un metodo di estensione a ogni progetto, oppure c) richiedono di evitare null IEnumerables (Pffft! Puh-LEAZE! SMH.) per iniziare con (cuz, nullsignifica N / A, mentre elenco vuoto significa, è appl. ma è attualmente, beh, vuoto !, cioè un dipendente potrebbe avere Commissioni N / A per le non vendite o vuote per le vendite quando non ne ha guadagnate).
Tom

10

In realtà c'è una richiesta di funzionalità su quel @Connect: http://connect.microsoft.com/VisualStudio/feedback/details/93497/foreach-should-check-for-null

E la risposta è abbastanza logica:

Penso che la maggior parte dei cicli foreach siano scritti con l'intento di iterare una raccolta non nulla. Se provi a iterare tramite null dovresti ottenere la tua eccezione, in modo da poter correggere il tuo codice.


Immagino che ci siano pro e contro per questo, quindi hanno deciso di mantenerlo come era stato progettato in primo luogo. dopotutto, il foreach è solo un po 'di zucchero sintattico. se avessi chiamato items.GetEnumerator () che sarebbe andato in crash anche se gli elementi erano nulli, quindi dovevi testarlo prima.
Marius Bancila

6

Puoi sempre provarlo con una lista nulla ... ma questo è quello che ho trovato sul sito di msdn

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

Se l'espressione ha il valore null, viene generata un'eccezione System.NullReferenceException.


2

Non è superfluo. In fase di esecuzione verrà eseguito il cast degli elementi a un oggetto IEnumerable e verrà chiamato il relativo metodo GetEnumerator. Ciò provocherà una dereferenziazione degli elementi che falliranno


1
1) La sequenza non sarà necessariamente lanciata IEnumerablee 2) È una decisione progettuale farla lanciare. C # potrebbe facilmente inserire quel nullcontrollo se gli sviluppatori lo considerassero una buona idea.
CodesInChaos

2

Puoi incapsulare il controllo null in un metodo di estensione e utilizzare un lambda:

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

Il codice diventa:

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

If può essere ancora più conciso se vuoi semplicemente chiamare un metodo che prende un articolo e restituisce void:

items.ForEach(MethodThatTakesAnItem);

1

Ne hai bisogno. Otterrai un'eccezione quando foreachaccedi al contenitore per impostare l'iterazione in caso contrario.

Sotto le coperte, foreachutilizza un'interfaccia implementata sulla classe di raccolta per eseguire l'iterazione. L'interfaccia equivalente generica è qui .

L'istruzione foreach del linguaggio C # (per ciascuno in Visual Basic) nasconde la complessità degli enumeratori. Pertanto, si consiglia di utilizzare foreach invece di manipolare direttamente l'enumeratore.


1
Proprio come nota che tecnicamente non usa l'interfaccia, usa la digitazione a papera : blogs.msdn.com/b/kcwalina/archive/2007/07/18/ducknotation.aspx le interfacce assicurano che siano presenti i metodi e le proprietà giusti tuttavia, e aiuta la comprensione dell'intento. così come l'uso all'esterno per ogni ...
ShuggyCoUk

0

Il test è necessario, perché se la raccolta è null, foreach genererà un'eccezione NullReferenceException. In realtà è abbastanza semplice provarlo.

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

0

il secondo lancerà una NullReferenceExceptioncon il messaggioObject reference not set to an instance of an object.


0

Come accennato qui, è necessario verificare se non è nullo.

Non utilizzare un'espressione che restituisce null.


0

In C # 6 puoi scrivere qc in questo modo:

// 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)
{
    //..
}

È fondamentalmente la soluzione di Vlad Bezden ma usando il ?? espressione per generare sempre un array che non è nullo e quindi sopravvive al foreach piuttosto che avere questo controllo all'interno della parentesi foreach.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.