Почему .NET foreach loop выбрасывает NullRefException, когда коллекция имеет значение null?
Поэтому я часто сталкиваюсь с этой ситуацией... где Do.Something(...)
возвращает нулевую коллекцию, например:
int[] returnArray = Do.Something(...);
Затем я пытаюсь использовать эту коллекцию так:
foreach (int i in returnArray)
{
// do some more stuff
}
Мне просто интересно, почему цикл foreach не работает в нулевой коллекции? Мне кажется логичным, что 0 итераций будут выполняться с нулевой коллекцией... вместо этого он выдает NullReferenceException
. Кто-нибудь знает, почему это может быть?
Это раздражает, когда я работаю с API-интерфейсами, которые не совсем понятны тем, что они возвращают, поэтому я получаю везде if (someCollection != null)
...
Изменить: Благодарим вас за объяснение, что foreach
использует GetEnumerator
, и если нет перечислителя, geteach будет терпеть неудачу. Наверное, я спрашиваю, почему язык/время выполнения не может или не будет выполнять нулевую проверку перед захватом перечислителя. Мне кажется, что поведение все равно будет хорошо определено.
Ответы
Ответ 1
Ну, короткий ответ - "потому, что это разработали дизайнеры компилятора". Однако, реалистично, ваш объект коллекции имеет значение NULL, поэтому компилятор не может заставить перечислитель перебирать коллекцию.
Если вам действительно нужно сделать что-то вроде этого, попробуйте нулевой оператор коалесцирования:
int[] array = null;
foreach (int i in array ?? Enumerable.Empty<int>())
{
System.Console.WriteLine(string.Format("{0}", i));
}
Ответ 2
Цикл A foreach
вызывает метод GetEnumerator
.
Если коллекция null
, этот вызов метода приводит к NullReferenceException
.
Плохая практика возвращает коллекцию null
; ваши методы должны возвращать пустую коллекцию.
Ответ 3
Существует большая разница между пустой коллекцией и пустой ссылкой на коллекцию.
Когда вы используете 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
}
Ответ 4
Другой способ расширения для этого:
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);
});
Ответ 5
Просто напишите метод расширения, чтобы помочь вам:
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);
}
}
}
Ответ 6
Потому что нулевая коллекция - это не то же самое, что пустая коллекция. Пустая коллекция - это объект коллекции без элементов; нулевой набор является несуществующим объектом.
Здесь что-то попробовать: Объявите две коллекции любого рода. Инициализируйте его так, чтобы он был пустым, а другой присвойте значение null
. Затем попробуйте добавить объект в обе коллекции и посмотреть, что произойдет.
Ответ 7
Ответ длинный, но я попытался сделать это следующим образом, чтобы просто исключить исключение нулевого указателя и может быть полезным для кого-то, кто использует оператор проверки нулевой последовательности С#.
//fragments is a list which can be null
fragments?.ForEach((obj) =>
{
//do something with obj
});
Ответ 8
Это ошибка Do.Something()
. Лучшей практикой здесь было бы вернуть массив размера 0 (что возможно) вместо нуля.
Ответ 9
Потому что за кулисами foreach
получает перечислитель, эквивалентный этому:
using (IEnumerator<int> enumerator = returnArray.getEnumerator()) {
while (enumerator.MoveNext()) {
int i = enumerator.Current;
// do some more stuff
}
}
Ответ 10
Я думаю, что объяснение того, почему выбрано исключение, очень ясен с ответами, представленными здесь. Я просто хочу дополнить то, как я обычно работаю с этими коллекциями. Потому что, несколько раз, я использую коллекцию более одного раза и должен каждый раз проверять значение null. Чтобы этого избежать, я делаю следующее:
var returnArray = DoSomething() ?? Enumerable.Empty<int>();
foreach (int i in returnArray)
{
// do some more stuff
}
Таким образом, мы можем использовать коллекцию столько, сколько хотим, не опасаясь исключения, и мы не будем интерпретировать код с чрезмерными условными утверждениями.
Использование оператора нулевой проверки ?.
также является отличным подходом. Но в случае массивов (например, пример в вопросе) он должен быть преобразован в Список до:
int[] returnArray = DoSomething();
returnArray?.ToList().ForEach((i) =>
{
// do some more stuff
});
Ответ 11
таким образом, вопрос действительно о "почему", и да, принятый ответ - это метатеоретически правильный ответ, но это не абсолютный правильный ответ. Также было дано много ответов для решения этой проблемы, но это все еще не решило проблему ОП, связанную с необходимостью включения дополнительного кода для его обработки. Поскольку эта проблема преследует меня, и я в некотором роде новичок в ASP.NET MVC, мне пришлось абсолютно точно понять, почему, чтобы я мог учесть эту "функцию" в будущем. Мне пришлось по-настоящему взглянуть на результаты отладки, чтобы понять, почему, и, обратив внимание, я могу рассказать вам настоящую причину.
Вот почему. При создании коллекции из запроса, например коллекции, созданной с использованием LINQ или EntityFramework, даже если в списке нет сущностей или элементов, он все равно имеет значение Count == 1, а значение первого индекса равно нулю.
Ответ 12
SPListItem item;
DataRow dr = datatable.NewRow();
dr["ID"] = (!Object.Equals(item["ID"], null)) ? item["ID"].ToString() : string.Empty;