当collection为null时,为什么.NET foreach循环抛出NullRefException?

时间:2010-06-21 20:09:35

标签: c# .net

所以我经常遇到这种情况...... Do.Something(...)返回一个空集合,如下所示:

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

然后,我尝试使用这个集合:

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

我只是好奇,为什么foreach循环不能对null集合进行操作?对我来说似乎合乎逻辑的是,使用null集合执行0次迭代...而是抛出NullReferenceException。任何人都知道为什么会这样?

这很烦人,因为我正在使用的API并不清楚它们返回的确切内容,所以我到处都是if (someCollection != null) ......

编辑:感谢大家解释foreach使用GetEnumerator,如果没有枚举器,foreach就会失败。我想我问为什么语言/运行时在抓取枚举器之前不能或不会进行空检查。在我看来,行为仍然会得到很好的定义。

11 个答案:

答案 0 :(得分:218)

嗯,简短的回答是“因为这是编译器设计者设计它的方式。”但实际上,您的集合对象为null,因此编译器无法让枚举器循环遍历集合。

如果你真的需要做这样的事情,试试null合并算子:

    int[] array = null;

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

答案 1 :(得分:140)

foreach循环调用GetEnumerator方法 如果集合为null,则此方法调用会产生NullReferenceException

返回null集合是不好的做法;你的方法应该返回一个空集合。

答案 2 :(得分:44)

空集合与集合的空引用之间存在很大差异。

在内部使用foreach时,这是调用IEnumerable的GetEnumerator()方法。当引用为null时,这将引发此异常。

但是,空IEnumerableIEnumerable<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
}

答案 3 :(得分:8)

解决此问题的另一种扩展方法:

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

答案 4 :(得分:7)

它正在回答很久但我试图通过以下方式做到这一点,以避免空指针异常,并可能对使用C#null检查运算符的人有用。

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

答案 5 :(得分: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 :(得分:5)

因为null集合与空集合不同。空集合是没有元素的集合对象; null集合是一个不存在的对象。

这里有一些尝试:声明两个任何类型的集合。正常初始化一个以使其为空,并为另一个赋值null。然后尝试将对象添加到两个集合中,看看会发生什么。

答案 7 :(得分:3)

这是Do.Something()的错误。这里最好的做法是返回一个大小为0的数组(可能)而不是null。

答案 8 :(得分:2)

因为幕后foreach获得了一个枚举器,相当于:

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

答案 9 :(得分:1)

我认为,为什么会抛出异常的解释非常清楚,这里提供的答案。我只想补充我通常与theese系列合作的方式。因为,有时候,我会多次使用该集合,并且必须每次测试是否为null。为避免这种情况,我会执行以下操作:

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

答案 10 :(得分:-1)

SPListItem item;
DataRow dr = datatable.NewRow();

dr["ID"] = (!Object.Equals(item["ID"], null)) ? item["ID"].ToString() : string.Empty;