1

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
M Pal
  • 11
  • 1
  • 2
  • 1
    Does this answer your question? [What is a NullReferenceException, and how do I fix it?](https://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it) – Sir Rufo Jul 02 '20 at 06:37
  • @SirRufo no, I know what NullReferenceException is, however I do not know why this happens. – M Pal Jul 02 '20 at 06:49
  • Read the answer from that link and you will know how to fix it – Sir Rufo Jul 02 '20 at 06:53

3 Answers3

4

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>())
Jon Skeet
  • 1,261,211
  • 792
  • 8,724
  • 8,929
  • Oh, I thought that SelectMany would take two items of first instance and simply add a null at last place (merge of both collections) so it would be caught by ``if (item != null)```, not that it would try to iterate null. – M Pal Jul 02 '20 at 06:48
  • 1
    @MPal: No, `SelectMany` has never converted "a null reference" into "a collection with a single element of the default value" before flattening. I could understand it *possibly* automatically treating a null reference as if it were an *empty* collection, but it doesn't. It just behaves as if you've got two `foreach` loops, effectively (one nested in the other) - and that would throw a `NullReferenceException` too. – Jon Skeet Jul 02 '20 at 06:56
0

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.

Kirin Yao
  • 1,506
  • 2
  • 13
  • 21
0

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.

Jirka Hanika
  • 12,574
  • 3
  • 39
  • 68