Почему? does.NET foreach loop throw NullRefException когда коллекция равна null?



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



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


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



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


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



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



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

416   11  

11 ответов:

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

Если вам действительно нужно сделать что-то вроде этого, попробуйте оператор null coalescing:

    int[] array = null;

    foreach (int i in array ?? Enumerable.Empty<int>())
    {
        System.Console.WriteLine(string.Format("{0}", i));
    }

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

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

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

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

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


Edit:

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

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
}

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

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

просто написать метод расширения, чтобы помочь вам:

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

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

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

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

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

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

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

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

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

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

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

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

С помощью оператора проверки null ?. это также отличный подход. Но, в случае массивов (как пример в вопросе), он должен быть преобразован в список перед:

    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;

Comments

    Ничего не найдено.