【问题标题】:Foreach throws NullReferenceException on not null IEnumerable with elementsForeach 在带有元素的非空 IEnumerable 上抛出 NullReferenceException
【发布时间】:2020-10-22 16:37:48
【问题描述】:

我在我的代码中遇到了与下面代码中类似的情况。问题是由于某种原因在 foreach 循环中迭代会抛出 NullReferenceException

我的问题是,为什么会这样?

如果我自己创建了返回空元素的迭代器,foreach 会处理它,而只需 prints 空行。

以下代码的结果是test, test, NullReferenceException

using System;
using System.Collections.Generic;
using System.Linq;

public class NestedB
{
    public string Test {get;set;}
}

public class NestedA
{
    public List<NestedB> NestedCollection {get;set;}
}

public class Program
{
    public static void Main()
    {
        var listOfA = new List<NestedA>
        {
            new NestedA
            {
                NestedCollection = new List<NestedB> 
                {
                    new NestedB {Test = "test"},
                    new NestedB {Test = "test"}
                }
            },
            new NestedA ()
        };
        
        var listOfB = listOfA.SelectMany(x => x.NestedCollection);
        
        foreach (var item in listOfB)
        {
            if (item != null)
            {
                Console.WriteLine(item.Test);
            }
        }
    }
    
}

堆栈跟踪

Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
   at System.Linq.Enumerable.SelectManySingleSelectorIterator`2.MoveNext()
   at Program.Main()
Command terminated by signal 6

【问题讨论】:

标签: c# .net exception nullpointerexception


【解决方案1】:

补充现有答案:

编译优化和运行时优化可能会导致报告的行号不准确。不仅要检查可枚举,还要检查 foreach 主体中可能的取消引用空值,尤其是在迭代(静态声明的)数组时。

【讨论】:

    【解决方案2】:

    SelectMany 的实现是这样的:

    public static IEnumerable<TResult> SelectMany<TSource, TResult>(
      this IEnumerable<TSource> source,
      Func<TSource, IEnumerable<TResult>> selector)
    {
      if (source == null)
        throw Error.ArgumentNull(nameof (source));
      if (selector == null)
        throw Error.ArgumentNull(nameof (selector));
      return Enumerable.SelectManyIterator<TSource, TResult>(source, selector);
    }
    
    private static IEnumerable<TResult> SelectManyIterator<TSource, TResult>(
      IEnumerable<TSource> source,
      Func<TSource, IEnumerable<TResult>> selector)
    {
      foreach (TSource source1 in source)
      {
        foreach (TResult result in selector(source1)) // The error throws here
          yield return result;
      }
    }
    

    注意注释行。 selector(source1) 将为第二个 NestedA 项返回 null,并且此行将尝试获取 null 项的枚举数 (foreach)。这就是你得到错误的原因。

    【讨论】:

      【解决方案3】:

      这就是问题所在:

      listOfA.SelectMany(x => x.NestedCollection)
      

      您的第二个 NestedA 实例没有 NestedCollection,因此它试图查找“空引用中的所有项目”。如果您手动执行此操作,您将遇到完全相同的问题:

      var nestedA = new NestedA();
      // This will throw an exception, because nestedA.NestedCollectoin is null
      foreach (var nestedB in nestedA.NestedCollection)
      {
      }
      

      对此最简单的解决方法是将NestedCollection 设为只读属性,但将其初始化为:

      public List<NestedB> NestedCollection { get; } = new List<NestedB>();
      

      然后您需要修改您的第一个 NestedA 的初始化以使用集合初始化器:

      new NestedA
      {
          NestedCollection =
          {
              new NestedB { Test = "test" },
              new NestedB { Test = "test" }
          }
      }
      

      如果您不想这样做,您可以改为更改SelectMany 调用:

      var listOfB = listOfA.SelectMany(x => x.NestedCollection ?? Enumerable.Empty<NestedB>())
      

      【讨论】:

      • 哦,我认为 SelectMany 会采用两个第一个实例,并在最后一个位置简单地添加一个 null(两个集合的合并),这样它就会被 ``if (item != null) ```,并不是说它会尝试迭代null。
      • @MPal:不,SelectMany 在展平之前从未将“空引用”转换为“具有单个默认值元素的集合”。我可以理解它可能会自动将空引用视为集合,但事实并非如此。它只是表现得好像你有两个 foreach 循环,有效地(一个嵌套在另一个中) - 这也会抛出一个 NullReferenceException
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-05
      • 1970-01-01
      • 2014-06-15
      • 1970-01-01
      • 1970-01-01
      • 2021-10-02
      相关资源
      最近更新 更多