简体   繁体   中英

Foreach throws NullReferenceException on not null IEnumerable with elements

I have encountered in my code similiar situation to the one presented in code below. The problem is that for some reason iterating in foreach loop throws NullReferenceException .

My question is, why this happens?

If I create iterator that returns empty element myself, foreach handles it, and simply prints empty line.

Result of following code is : 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);
            }
        }
    }
    
}

Stacktrace :

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

This is the problem:

listOfA.SelectMany(x => x.NestedCollection)

Your second NestedA instance doesn't have a NestedCollection , so it's trying to find "all the items in a null reference". You'd have exactly the same problem if you did this manually:

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

The simplest fix to this would be to make NestedCollection a read-only property, but initialize it to start with:

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

Then you'll need to modify the initialization of your first NestedA to use a collection initializer:

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

If you don't want to do that, you could change the SelectMany call instead:

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

The implementation of SelectMany is something like this:

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

Note the comment line. The selector(source1) will return a null for the second NestedA item and this line will try to get the enumerator of the null item( foreach ). That's why you got the error.

To complement the existing answers:

Compilation optimization and runtime optimization can cause inaccuracies in the reported line numbers. It is useful to examine not just the enumerable, but also the foreach body for possible dereferenced nulls, especially if iterating over a (statically declared) array.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM