Verificare la presenza di null nel ciclo foreach


91

Esiste un modo migliore per eseguire le seguenti operazioni:
Ho bisogno di un controllo per verificare che non sia presente alcun valore null sul file.Headers prima di procedere con il ciclo

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

In breve, sembra un po 'brutto scrivere il foreach all'interno di if a causa del livello di indentazione che si verifica nel mio codice.

È qualcosa che valuterebbe

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

possibile?


3
Si può avere uno sguardo qui: stackoverflow.com/questions/6937407/...
Adrian Faciu


1
@AdrianFaciu Penso che sia completamente diverso. La domanda controlla se la raccolta è nulla prima di eseguire l'operazione for-each. Il tuo collegamento controlla se l'elemento nella raccolta è nullo.
rikitikitik


1
C # 8 potrebbe semplicemente avere un foreach condizionale null di qualche tipo, ad esempio una sintassi come questa: foreach? (var i in collection) {} Penso che sia uno scenario abbastanza comune per giustificare questo, e date le recenti aggiunte con condizioni nulle al linguaggio ha senso qui?
mms

Risposte:


121

Proprio come una leggera aggiunta cosmetica al suggerimento di Rune, potresti creare il tuo metodo di estensione:

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

Quindi puoi scrivere:

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

Cambia il nome in base ai gusti :)


75

Supponendo che il tipo di elementi in file.Headers sia T, potresti farlo

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

questo creerà un enumerabile vuoto di T se file.Headers è nullo. Se il tipo di file è un tipo che possiedi, tuttavia, prenderei in considerazione la possibilità di cambiare il getter di Headers.nullèil valore di unknown quindi se possibile invece di usare null come "So che non ci sono elementi" quando null in realtà (/ originariamente) dovrebbe essere interpretato come "Non so se ci sono elementi" usa un set vuoto per mostrare che sai che non ci sono elementi nel set. Sarebbe anche DRY'er dal momento che non dovrai fare il controllo nullo così spesso.

EDIT come seguito al suggerimento di Jons, potresti anche creare un metodo di estensione cambiando il codice sopra in

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

Nel caso in cui non sia possibile modificare il getter, questo sarebbe il mio preferito poiché esprime l'intenzione in modo più chiaro dando un nome all'operazione (OrEmptyIfNull)

Il metodo di estensione sopra menzionato potrebbe rendere impossibili da rilevare alcune ottimizzazioni per l'ottimizzatore. In particolare, quelli correlati a IList che utilizzano il sovraccarico del metodo possono essere eliminati

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

La risposta di @ kjbartel (su "stackoverflow.com/a/32134295/401246" è la soluzione migliore, perché non: a) comporta il degrado delle prestazioni (anche quando non null) degenerando l'intero loop su LCD di IEnumerable<T>(come usando ?? richiederebbe), b) richiedere l'aggiunta di un metodo di estensione a ogni progetto, oppure c) richiedere di evitare null IEnumerables(Pffft! Puh-LEAZE! SMH.) per iniziare (cuz nullsignifica N / A, mentre elenco vuoto significa, è applicabile ma è attualmente, beh, vuoto !, ovvero un dipendente potrebbe avere Commissioni N / A per non vendite o vuote per vendite quando non ne ha guadagnate).
Tom

@Tom a parte un controllo nullo non c'è penalità per i casi in cui l'enumeratore non è nullo. Evitare quel controllo assicurandosi anche che l'enumerabile non sia nullo è impossibile. Il codice precedente richiede che le intestazioni IEnumerable siano più restrittive dei foreachrequisiti ma meno restrittive del requisito della List<T>risposta a cui si collega. Che hanno la stessa penalizzazione delle prestazioni di verificare se l'enumerabile è nullo.
Rune FS

Stavo basando il problema "LCD" sul commento di Eric Lippert sulla risposta di Vlad Bezden nello stesso thread della risposta di kjbartel: "@CodeInChaos: Ah, ora capisco il tuo punto. Quando il compilatore può rilevare che" foreach "sta iterando su un List <T> o un array, può ottimizzare il foreach per utilizzare enumeratori di tipo valore o effettivamente generare un ciclo "for". Quando è costretto a enumerare su un elenco o sulla sequenza vuota, deve tornare al " minimo comune denominatore "codegen, che in alcuni casi può essere più lento e produrre più pressione sulla memoria ....". D'accordo che richiede List<T>.
Tom

@tom La premessa della risposta è che file.Headers sono un IEnumerable <T>, nel qual caso il compilatore non può fare l'ottimizzazione. Tuttavia, è piuttosto semplice estendere la soluzione del metodo di estensione per evitarlo. Vedi modifica
Rune FS

19

Francamente, lo consiglio: basta succhiare il nulltest. Un nulltest è solo un brfalseo brfalse.s; tutto il resto sta per coinvolgere molto di più di lavoro (test, le assegnazioni, chiamate di metodo in più, inutile GetEnumerator(), MoveNext(), Dispose()sul iteratore, ecc).

Un iftest è semplice, ovvio ed efficiente.


1
Fai un punto interessante, Marc, tuttavia, attualmente sto cercando di ridurre i livelli di rientro del codice. Ma terrò a mente il tuo commento quando avrò bisogno di prendere nota della performance.
Eminem

3
Solo una breve nota su questo Marc .. anni dopo aver posto questa domanda e aver bisogno di implementare alcuni miglioramenti delle prestazioni, il tuo consiglio è stato estremamente utile. Grazie
Eminem

12

il "se" prima dell'iterazione va bene, poche di quelle semantiche "carine" possono rendere il codice meno leggibile.

comunque, se il rientro disturba il tuo, puoi cambiare if per verificare:

if(file.Headers == null)  
   return;

e arriverai al ciclo foreach solo quando c'è un valore vero nella proprietà headers.

un'altra opzione a cui posso pensare è usare l'operatore di coalescenza null all'interno del tuo ciclo foreach e per evitare completamente il controllo null. campione:

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

(sostituisci la collezione con il tuo vero oggetto / tipo)


La tua prima opzione non funzionerà se c'è un codice al di fuori dell'istruzione if.
rikitikitik

Sono d'accordo, l'istruzione if è facile ed economica da implementare rispetto alla creazione di un nuovo elenco, solo per l'abbellimento del codice.
Andreas Johansson

10

Utilizzo dell'operatore Null-conditional e ForEach () che funziona più velocemente del ciclo foreach standard.
Tuttavia, devi trasmettere la raccolta a List.

   listOfItems?.ForEach(item => // ... );

4
Per favore aggiungi qualche spiegazione intorno alla tua risposta, affermando esplicitamente perché questa soluzione funziona, piuttosto che solo una
riga di

soluzione migliore per il mio caso
Josef Henn

3

Sto usando un piccolo metodo di estensione per questi scenari:

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

Dato che Headers è di tipo list, puoi fare quanto segue:

foreach(var h in (file.Headers.EnsureNotNull()))
{
  //do stuff
}

1
puoi utilizzare l' ??operatore e abbreviare la dichiarazione di ritorno areturn list ?? new List<T>;
Rune FS

1
@wolfgangziegler, se ho capito bene il test per il nulltuo campione file.Headers.EnsureNotNull() != nullnon è necessario ed è addirittura sbagliato?
Remko Jansen

0

Per alcuni casi preferirei leggermente un'altra variante generica, supponendo che, di regola, i costruttori di raccolta predefiniti restituiscano istanze vuote.

Sarebbe meglio nominare questo metodo NewIfDefault. Può essere utile non solo per le raccolte, quindi il vincolo di tipo IEnumerable<T>è forse ridondante.

public static TCollection EmptyIfDefault<TCollection, T>(this TCollection collection)
        where TCollection: class, IEnumerable<T>, new()
    {
        return collection ?? new TCollection();
    }
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.