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

Pol*_*878 218 .net c#

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

int[] returnArray = Do.Something(...);
Run Code Online (Sandbox Code Playgroud)

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

foreach (int i in returnArray)
{
    // do some more stuff
}
Run Code Online (Sandbox Code Playgroud)

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

这很烦人,因为我正在处理那些不确定它们返回的API,所以我最终if (someCollection != null)到处都是......

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

Rob*_*cus 239

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

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

int[] array = null;

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

  • 我不相信.查看生成的IL,循环是在null比较之后. (16认同)
  • 神圣的死灵...有时你必须看看IL,看看编译器正在做什么来弄清楚是否有任何效率命中.User919426询问是否对每次迭代进行了检查.虽然答案对某些人来说可能是显而易见的,但对每个人来说都不是很明显,并且提供了一个暗示,即看IL会告诉你编译器在做什么,这有助于人们在将来捕捉自己. (8认同)
  • 请原谅我的无知,但这有效吗?是否不会在每次迭代中进行比较? (2认同)
  • @Robaticus(甚至为什么后来)IL 看起来是为什么,因为规范是这样说的。语法糖(又名 foreach)的扩展是对“in”右侧的表达式求值,并在结果上调用`GetEnumerator` (2认同)
  • @RuneFS - 完全正确.理解规范或查看IL是一种找出"原因"的方法.或者评估两种不同的C#方法是否归结为相同的IL.基本上,这就是我对Shimmy的观点. (2认同)
  • 可能与未来的读者相关 - 为什么你不能对字典做同样的事情:/sf/ask/730189701/ -of-string-string-eas (2认同)
  • 您也可以称他们为语言设计者,而不是编译器设计者。官方 C# 语言规范特别指出:在“foreach”语句的描述中(其中“x”是您 foreach 的内容),如果“x”的值为“null”,则会在运行时抛出“System.NullReferenceException”。 )。即使没有这句话,从规范中也可以得出事实就是如此。因此任何正确的 C# 实现都必须具有这种行为。 (2认同)

SLa*_*aks 143

一个foreach循环中调用的GetEnumerator方法.
如果是集合null,则此方法调用将导致a NullReferenceException.

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

  • @Polaris,null合并操作员来救援!`int [] returnArray = Do.Something()?? new int [] {};` (19认同)
  • 我同意,应该总是返回空集合...但是我没有写这些方法:) (7认同)
  • +1喜欢返回空集合的提示而不是null.谢谢. (3认同)
  • 我不同意一种不好的做法:参见⇒如果一个函数失败,它可能会返回一个空集合——它是对构造函数、内存分配的调用,也许还有一堆要执行的代码。或者您可以只返回“null”→显然只有一个代码要返回,并且需要一个非常短的代码来检查参数是否为“null”。这只是一场表演。 (3认同)
  • 或者:`......?new int [0]`. (2认同)

Ree*_*sey 47

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

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

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


编辑:

就个人而言,如果您需要解决此问题,我建议使用扩展方法:

public static IEnumerable<T> AsNotNull<T>(this IEnumerable<T> original)
{
     return original ?? Enumerable.Empty<T>();
}
Run Code Online (Sandbox Code Playgroud)

然后你可以打电话:

foreach (int i in returnArray.AsNotNull())
{
    // do some more stuff
}
Run Code Online (Sandbox Code Playgroud)

  • @ Polaris878:因为它从未打算与null集合一起使用.这是IMO,这是一件好事 - 因为空引用和空集合应该分开处理.如果你想解决这个问题,有办法....我会编辑以显示另一个选项... (11认同)
  • 是的,但为什么在获得调查员之前不会进行空检查? (3认同)
  • @ Polaris878:我想,我想到的方式,为集合返回null是一个错误.现在的方式,运行时在这种情况下给你一个有意义的异常,但如果你不喜欢这种行为,它很容易解决(即:上面).如果编译器隐藏了这个,你就会在运行时丢失错误检查,但是没有办法"关闭它"...... (2认同)

Dev*_*esh 12

它是长期回答,但我试图通过以下方式做到这一点,以避免空指针异常,并可能对使用C#null check运算符的人有用?

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

  • @kjbartel 比你早了一年多(参见“ /sf/answers/2249400681/ ”)。;)这是最好的解决方案,因为它不会:a)涉及将整个循环推广到“Enumerable”的 LCD 的性能下降(即使不是“null”)(如使用“??”),b ) 要求向每个项目添加扩展方法,并且 c) 要求首先避免“null”“IEnumerable”(Pffft!Puh-LEAZE!SMH。)。 (2认同)

Jay*_*Jay 10

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

public static void ForEach<T>(this IEnumerable<T> items, Action<T> action)
{
    if(items == null) return;
    foreach (var item in items) action(item);
}
Run Code Online (Sandbox Code Playgroud)

以几种方式消费:

(1)采用以下方法T:

returnArray.ForEach(Console.WriteLine);
Run Code Online (Sandbox Code Playgroud)

(2)用表达式:

returnArray.ForEach(i => UpdateStatus(string.Format("{0}% complete", i)));
Run Code Online (Sandbox Code Playgroud)

(3)采用多线匿名方法

int toCompare = 10;
returnArray.ForEach(i =>
{
    var thisInt = i;
    var next = i++;
    if(next > 10) Console.WriteLine("Match: {0}", i);
});
Run Code Online (Sandbox Code Playgroud)


BFr*_*ree 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);
      }
   }
}
Run Code Online (Sandbox Code Playgroud)


JAB*_*JAB 5

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

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