代码之家  ›  专栏  ›  技术社区  ›  DigitalZebra

为什么.NET foreach循环在集合为空时引发NullRefException?

  •  194
  • DigitalZebra  · 技术社区  · 16 年前

    所以我经常遇到这种情况…在哪里? 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就会失败。我想我在问,为什么语言/运行时不能或不会在抓取枚举器之前执行空检查。在我看来,这种行为仍将得到很好的定义。

    11 回复  |  直到 7 年前
        1
  •  212
  •   Shimmy Weitzhandler 500 - Internal Server Error    9 年前

    好吧,简短的回答是“因为编译器设计人员就是这样设计的。”但实际上,集合对象是空的,所以编译器没有办法让枚举器在集合中循环。

    如果确实需要这样做,请尝试使用空合并运算符:

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

    一 foreach 循环调用 GetEnumerator 方法。
    如果集合是 null ,此方法调用导致 NullReferenceException .

    返回 无效的 集合;您的方法应返回空集合。

        3
  •  44
  •   huysentruitw Marc Gravell    8 年前

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

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

    但是,空的 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
    }
    
        4
  •  8
  •   Jay    9 年前

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

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

    (二)有下列用语:

    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);
    });
    
        5
  •  5
  •   BFree    16 年前

    只需编写一个扩展方法来帮助您:

    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
  •   JAB    16 年前

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

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

        7
  •  5
  •   Devesh    9 年前

    这是很早以前的答案,但我尝试用以下方法来避免空指针异常,并且可能对使用C空检查运算符的人有用?.

         //fragments is a list which can be null
         fragments?.ForEach((obj) =>
            {
                //do something with obj
            });
    
        8
  •  3
  •   Henk Holterman    16 年前

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

        9
  •  2
  •   Lucero    16 年前

    因为在幕后 foreach 获取枚举器,等效于:

    using (IEnumerator<int> enumerator = returnArray.getEnumerator()) {
        while (enumerator.MoveNext()) {
            int i = enumerator.Current;
            // do some more stuff
        }
    }
    
        10
  •  1
  •   Alielson Piffer Thomas Levesque    9 年前

    我认为对于为什么抛出异常的解释非常清楚,这里提供了答案。我只是想补充一下我通常处理废弃收藏品的方式。因为,有时,我会多次使用集合,每次都必须测试是否为空。为了避免出现这种情况,我会执行以下操作:

        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
        });
    
        11
  •  -1
  •   Tunaki    10 年前
    SPListItem item;
    DataRow dr = datatable.NewRow();
    
    dr["ID"] = (!Object.Equals(item["ID"], null)) ? item["ID"].ToString() : string.Empty;