Почему цикл .NET foreach генерирует исключение NullRefException, когда коллекция пуста? - PullRequest
204 голосов
/ 22 июня 2010

Поэтому я часто сталкиваюсь с такой ситуацией ... где Do.Something(...) возвращает нулевую коллекцию, например так:

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

Затем я пытаюсь использовать эту коллекцию следующим образом:

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

Мне просто любопытно, почему цикл foreach не может работать с нулевой коллекцией?Мне кажется логичным, что 0 итераций будут выполняться с нулевой коллекцией ... вместо этого он выдает NullReferenceException.Кто-нибудь знает, почему это может быть?

Это раздражает, так как я работаю с API, которые не совсем точно понимают, что они возвращают, поэтому я получаю if (someCollection != null) везде ...

Редактировать: Спасибо всем за объяснение, что foreach использует GetEnumerator, и если нет перечислителя, который можно получить, foreach потерпит неудачу.Я предполагаю, что спрашиваю, почему язык / среда выполнения не могут или не будут выполнять нулевую проверку перед захватом перечислителя.Мне кажется, что поведение все еще будет хорошо определено.

Ответы [ 11 ]

225 голосов
/ 22 июня 2010

Ну, краткий ответ - «потому что так разработчики компилятора разработали его». Реально, однако, ваш объект коллекции имеет значение null, поэтому компилятор не может заставить перечислитель перебрать коллекцию.

Если вам действительно нужно сделать что-то подобное, попробуйте оператор объединения нулей:

    int[] array = null;

    foreach (int i in array ?? Enumerable.Empty<int>())
    {
        System.Console.WriteLine(string.Format("{0}", i));
    }
141 голосов
/ 22 июня 2010
Цикл

A foreach вызывает метод GetEnumerator.
Если набор равен null, этот вызов метода приводит к NullReferenceException.

Возвращать * плохая практикаКоллекция 1008 *;вместо этого ваши методы должны возвращать пустую коллекцию.

44 голосов
/ 22 июня 2010

Существует большая разница между пустой коллекцией и пустой ссылкой на коллекцию.

При внутреннем использовании foreach это вызывает метод IEnumerable GetEnumerator (),Если ссылка нулевая, это вызовет это исключение.

Однако вполне допустимо иметь пустое значение IEnumerable или IEnumerable<T>.В этом случае foreach не будет «перебирать» что-либо (так как коллекция пуста), но также не будет генерировать, поскольку это совершенно правильный сценарий.


Редактировать:

Лично, если вам нужно обойти это, я бы порекомендовал метод расширения:

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

Затем вы можете просто позвонить:

foreach (int i in returnArray.AsNotNull())
{
    // do some more stuff
}
9 голосов
/ 22 июня 2010

Еще один метод расширения, чтобы обойти это:

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

Использовать несколькими способами:

(1) с методом, который принимает T:

returnArray.ForEach(Console.WriteLine);

(2) с выражением:

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

(3) с многострочным анонимным методом

int toCompare = 10;
returnArray.ForEach(i =>
{
    var thisInt = i;
    var next = i++;
    if(next > 10) Console.WriteLine("Match: {0}", i);
});
7 голосов
/ 11 ноября 2016

Это ответ давно, но я попытался сделать это следующим образом, чтобы просто избежать исключения нулевого указателя, и может быть полезно для кого-то, использующего оператор проверки нуля C #? *

     //fragments is a list which can be null
     fragments?.ForEach((obj) =>
        {
            //do something with obj
        });
5 голосов
/ 22 июня 2010

Потому что пустая коллекция - это не то же самое, что пустая коллекция.Пустая коллекция - это объект коллекции без элементов;Нулевая коллекция - это несуществующий объект.

Вот что можно попробовать: Объявить две коллекции любого рода.Инициализируйте один обычно так, чтобы он был пустым, и назначьте другому значение null.Затем попробуйте добавить объект в обе коллекции и посмотрите, что произойдет.

5 голосов
/ 22 июня 2010

Просто напишите метод расширения, который поможет вам:

public static class Extensions
{
   public static void ForEachWithNull<T>(this IEnumerable<T> source, Action<T> action)
   {
      if(source == null)
      {
         return;
      }

      foreach(var item in source)
      {
         action(item);
      }
   }
}
3 голосов
/ 22 июня 2010

Это вина Do.Something().Лучшей практикой здесь было бы возвращать массив размера 0 (это возможно) вместо нуля.

2 голосов
/ 22 июня 2010

Поскольку за кадром foreach получает перечислитель, эквивалентный этому:

using (IEnumerator<int> enumerator = returnArray.getEnumerator()) {
    while (enumerator.MoveNext()) {
        int i = enumerator.Current;
        // do some more stuff
    }
}
1 голос
/ 28 июня 2017

Я думаю, что объяснение, почему выбрасывается исключение, очень ясно с ответами, представленными здесь. Я просто хочу дополнить то, как я обычно работаю с этими коллекциями. Потому что иногда я использую коллекцию более одного раза и должен каждый раз проверять, не равен ли ей ноль. Чтобы избежать этого, я делаю следующее:

    var returnArray = DoSomething() ?? Enumerable.Empty<int>();

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

Таким образом, мы можем использовать коллекцию столько, сколько захотим, не опасаясь исключения, и мы не добавляем в код чрезмерные условные выражения.

Использование оператора проверки нуля ?. также является отличным подходом. Но, в случае массивов (как пример в вопросе), он должен быть преобразован в List раньше:

    int[] returnArray = DoSomething();

    returnArray?.ToList().ForEach((i) =>
    {
        // do some more stuff
    });
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...