为什么.NET的foreach循环抛出NullRefException当收集为空?

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

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

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

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

我只是好奇,为什么不能在一个空收集的foreach循环操作? 对我来说这似乎合乎逻辑,0迭代将被执行一个NullReferenceException …相反,它会抛出一个NullReferenceException 。 任何人都知道为什么这可能是?

这是令人讨厌的,因为我正在使用那些不清楚它们返回的API,所以我最终以if (someCollection != null)到处都是。

编辑:谢谢大家解释, foreach使用GetEnumerator ,如果没有枚举器得到,foreach将失败。 我想我问为什么语言/运行时不能或不会做一个空检查,然后抓住枚举。 在我看来,这种行为仍然是明确的。

那么最简单的答案就是“因为编译器devise者就是这样devise的”。 实际上,你的集合对象是空的,所以编译器没有办法让枚举器遍历集合。

如果你确实需要做这样的事情,可以尝试使用null合并运算符:

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

一个foreach循环调用GetEnumerator方法。
如果集合为null ,则此方法调用NullReferenceException

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

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

当你使用foreach ,在内部,这是调用IEnumerable的GetEnumerator ()方法。 当引用为空时,这将引发这个exception。

但是,有一个空的IEnumerableIEnumerable<T>是完全有效的。 在这种情况下,foreach不会“迭代”任何东西(因为集合是空的),但是它也不会抛出,因为这是一个完全有效的场景。


编辑:

就个人而言,如果你需要解决这个问题,我会推荐一个扩展方法:

 public static IEnumerable<T> AsNotNull<T>(this IEnumerable<T> original) { return original ?? new T[0]; } 

然后你可以打电话给:

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

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

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

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

 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)用expression式:

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

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

这是回答久了,但我已经试图做到这一点,以避免空指针exception,可能是有用的人使用C#空检查运算符?

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

因为在幕后, foreach获得了一个统计员,相当于这个:

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

我想这里提供的答案很清楚为什么抛出exception的解释。 我只是想补充一下我通常与这些藏品合作的方式。 因为,有时候我多次使用集合,每次都必须testing是否为空。 为了避免这一点,我做了以下几点:

  var returnArray = DoSomething() ?? Enumerable.Empty<int>(); foreach (int i in returnArray) { // do some more stuff } 

通过这种方式,我们可以尽可能多地使用集合,而不用担心exception,并且不会用过多的条件语句来监视代码。

使用空检查运算符?. 也是一个很好的方法。 但是,在数组的情况下(如问题中的例子),应该在之前将其转换为List:

  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;