c# php - Почему.NET foreach loop выбрасывает NullRefException, когда коллекция имеет значение null?




(10)

Поэтому я часто сталкиваюсь с этой ситуацией ... где 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 потерпит неудачу. Наверное, я спрашиваю, почему язык / время выполнения не может или не будет выполнять нулевую проверку перед захватом перечислителя. Мне кажется, что поведение по-прежнему будет четко определено.


Answers

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

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

    int[] array = null;

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

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

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


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


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

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

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

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

Другой метод расширения для этого:

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

Потому что за кулисами 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
    }

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

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

    int[] returnArray = DoSomething();

    returnArray?.ToList().ForEach((i) =>
    {
        // do some more stuff
    });

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

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

Тем не менее, вполне допустимо иметь пустой 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
}

На самом деле очень сложно реализовать GetHashCode() правильно, потому что, помимо уже упомянутых правил Marc, хэш-код не должен меняться в течение всего жизненного цикла объекта. Поэтому поля, которые используются для вычисления хэш-кода, должны быть неизменными.

Наконец, я нашел решение этой проблемы, когда я работал с NHibernate. Мой подход заключается в вычислении хеш-кода из идентификатора объекта. Идентификатор может быть установлен только с помощью конструктора, поэтому, если вы хотите изменить ID, что очень маловероятно, вам нужно создать новый объект с новым идентификатором и, следовательно, новый хэш-код. Этот подход лучше всего работает с GUID, потому что вы можете предоставить конструктор без параметров, который случайным образом генерирует идентификатор.





c# .net