为什么。net foreach循环抛出NullRefException当集合为空时?

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

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

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

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

我只是好奇,为什么foreach循环不能操作一个空集合?在我看来,零次迭代将被一个空集合执行是合乎逻辑的……相反,它抛出NullReferenceException。有人知道为什么吗?

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

169455 次浏览

foreach循环调用GetEnumerator方法 如果集合为null,则此方法调用将导致NullReferenceException.

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

因为在幕后foreach获取了一个枚举数,等价于:

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

空集合和对集合的空引用之间有很大区别。

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

然而,有一个空的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
}

只要写一个扩展方法来帮助你:

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。然后尝试向两个集合添加一个对象,看看会发生什么。

这是Do.Something()的错。这里的最佳实践是返回一个大小为0的数组(这是可能的),而不是null。

简单的回答是“因为编译器设计者就是这么设计的。”但实际上,您的集合对象是空的,因此编译器无法让枚举器循环遍历集合。

如果你真的需要这样做,试试空合并运算符:

int[] array = null;


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

另一个扩展方法来解决这个问题:

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);
});
SPListItem item;
DataRow dr = datatable.NewRow();


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

它被回答了很长时间,但我已经尝试以以下方式这样做,以避免空指针异常,可能对使用c#空检查操作符的人有用。

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

我认为这里提供的答案对为什么抛出异常的解释非常清楚。我只是想补充一下我通常处理这些系列的方式。因为,有些时候,我不止一次地使用集合,并且每次都必须测试是否为空。为了避免这种情况,我采取了以下措施:

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