Perché il ciclo foreach .NET genera NullRefException quando la raccolta è nullo?

Quindi mi Do.Something(...) spesso in questa situazione … dove Do.Something(...) restituisce una raccolta nulla, in questo modo:

 int[] returnArray = Do.Something(...); 

Quindi, provo ad usare questa collezione in questo modo:

 foreach (int i in returnArray) { // do some more stuff } 

Sono solo curioso, perché un ciclo foreach non può funzionare su una raccolta nulla? Mi sembra logico che 0 iterazioni vengano eseguite con una raccolta null … invece lancia una NullReferenceException . Qualcuno sa perché questo potrebbe essere?

Questo è fastidioso perché sto lavorando con API che non sono chiare esattamente su ciò che restituiscono, quindi if (someCollection != null) con if (someCollection != null) ovunque …

Modifica: Grazie a tutti per aver spiegato che foreach utilizza GetEnumerator e se non è disponibile alcun enumeratore, il foreach fallirebbe. Suppongo che sto chiedendo perché la lingua / runtime non può o non vuole fare un controllo nullo prima di afferrare l’enumeratore. Mi sembra che il comportamento sarebbe ancora ben definito.

Bene, la risposta breve è “perché è così che i progettisti del compilatore lo hanno progettato”. Realisticamente, però, il tuo object collection è nullo, quindi non c’è modo per il compilatore di far scorrere l’enumeratore attraverso la collezione.

Se hai davvero bisogno di fare qualcosa di simile, prova l’operatore null coalescing:

  int[] array = null; foreach (int i in array ?? Enumerable.Empty()) { System.Console.WriteLine(string.Format("{0}", i)); } 

Un ciclo foreach chiama il metodo GetEnumerator .
Se la raccolta è null , questa chiamata al metodo genera una NullReferenceException .

È una ctriggers pratica restituire una raccolta null ; i tuoi metodi dovrebbero invece restituire una raccolta vuota.

C’è una grande differenza tra una raccolta vuota e un riferimento null a una collezione.

Quando si utilizza foreach , internamente, si chiama il metodo GetEnumerator () di IEnumerable. Quando il riferimento è nullo, questo solleverà questa eccezione.

Tuttavia, è perfettamente valido avere un object IEnumerable o IEnumerable vuoto. In questo caso, foreach non “eseguirà iterazioni” su nulla (poiché la raccolta è vuota), ma non genererà nemmeno un lancio, poiché si tratta di uno scenario perfettamente valido.


Modificare:

Personalmente, se hai bisogno di ovviare a questo, ti consigliamo un metodo di estensione:

 public static IEnumerable AsNotNull(this IEnumerable original) { return original ?? Enumerable.Empty(); } 

Puoi quindi solo chiamare:

 foreach (int i in returnArray.AsNotNull()) { // do some more stuff } 

Un altro metodo di estensione per aggirare questo problema:

 public static void ForEach(this IEnumerable items, Action action) { if(items == null) return; foreach (var item in items) action(item); } 

Consumare in diversi modi:

(1) con un metodo che accetta T :

 returnArray.ForEach(Console.WriteLine); 

(2) con un’espressione:

 returnArray.ForEach(i => UpdateStatus(string.Format("{0}% complete", i))); 

(3) con un metodo anonimo multilinea

 int toCompare = 10; returnArray.ForEach(i => { var thisInt = i; var next = i++; if(next > 10) Console.WriteLine("Match: {0}", i); }); 

Basta scrivere un metodo di estensione per aiutarti:

 public static class Extensions { public static void ForEachWithNull(this IEnumerable source, Action action) { if(source == null) { return; } foreach(var item in source) { action(item); } } } 

Perché una raccolta nulla non è la stessa cosa di una collezione vuota. Una collezione vuota è un object di raccolta senza elementi; una raccolta nullo è un object inesistente.

Ecco qualcosa da provare: dichiara due raccolte di qualsiasi tipo. Inizializzarne uno normalmente in modo che sia vuoto e assegnare all’altro il valore null . Quindi prova ad aggiungere un object ad entrambe le collezioni e vedere cosa succede.

Si sta rispondendo a lungo, ma ho cercato di farlo nel modo seguente per evitare l’eccezione del puntatore nullo e potrebbe essere utile per qualcuno che usa l’operatore di controllo null C #.

  //fragments is a list which can be null fragments?.ForEach((obj) => { //do something with obj }); 

È colpa di Do.Something() . La migliore pratica qui sarebbe quella di restituire una matrice di dimensione 0 (che è ansible) invece di null.

Perché dietro le quinte il foreach acquisisce un enumeratore, equivalente a questo:

 using (IEnumerator enumerator = returnArray.getEnumerator()) { while (enumerator.MoveNext()) { int i = enumerator.Current; // do some more stuff } } 

Penso che la spiegazione del perché venga lanciata un’eccezione è molto chiara con le risposte fornite qui. Vorrei solo completare il mio modo di lavorare di solito con queste collezioni. Perché, alcune volte, uso la raccolta più di una volta e devo verificare se null ogni volta. Per evitare ciò, faccio quanto segue:

  var returnArray = DoSomething() ?? Enumerable.Empty(); foreach (int i in returnArray) { // do some more stuff } 

In questo modo possiamo usare la collezione tanto quanto vogliamo senza temere l’eccezione e non polarizziamo il codice con dichiarazioni condizionali eccessive.

Utilizzando l’operatore di controllo nullo ?. è anche un ottimo approccio. Ma, in caso di matrici (come nell’esempio nella domanda), dovrebbe essere trasformato in Elenco prima:

  int[] returnArray = DoSomething(); returnArray?.ToList().ForEach((i) => { // do some more stuff }); 
 SPListItem item; DataRow dr = datatable.NewRow(); dr["ID"] = (!Object.Equals(item["ID"], null)) ? item["ID"].ToString() : string.Empty;