Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why the following C# code throws NullReferenceException on nullable DateTime initialization?

Here's some simple code:

    static void Main(string[] args)
    {
        var coll = new List<string> {"test", "test2", "test3"};

        var filteredColl = coll.Select(x => x).ToList();

        if (!filteredColl.Any())
        {
            DateTime? date = new DateTime();

            filteredColl = coll.Where(x => date.GetValueOrDefault().Date.ToString(CultureInfo.InvariantCulture) == x).Select(x => x).ToList();
        }
    }

The question is, why the following steps make it crash with NullReferenceException:

1) Breakpoint to the if

Breakpoint

2) Set the next execution point:

Execution point

3) Try to continue with F10:

Exception

If I comment out the last line of the code, it won't crash.

Update: Here's the stack trace:

System.NullReferenceException was unhandled   HResult=-2147467261  
Message=Object reference not set to an instance of an object.  
Source=ConsoleApplication28   StackTrace:
       at ConsoleApplication28.Program.Main(String[] args) in Program.cs: line 21
       at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()   InnerException:
like image 611
Mikael Koskinen Avatar asked Dec 20 '22 00:12

Mikael Koskinen


1 Answers

This is a side-effect of moving the execution point inside a context that declares a captured variable scope. It would be reasonable to report it as an IDE bug, but it would not be trivial to fix. Basically, date is not a variable - it is a field on a capture-context, because of the lambda. The compiler essentially does:

if (!filteredColl.Any())
{
    var ctx = new SomeCaptureContext(); // <== invented by the compiler
    ctx.date = new DateTime();

    filteredColl = coll.Where(ctx.SomePredicate).Select(x => x).ToList();
}

where SomePredicate is:

class SomeCaptureContext {
    public DateTime? date; // yes, a public field - needs to support 'ref' etc
    public bool SomePredicate(string x) // the actual name is horrible
    {
        return this.date.GetValueOrDefault()
              .Date.ToString(CultureInfo.InvariantCulture) == x;
    }
}

The problem here is that when you drag the execution position to:

DateTime? date = new DateTime();

You are actually (in IL terms) dragging it to the line:

ctx.date = new DateTime();

The capture-context line immediately before that, i.e.

var ctx = new SomeCaptureContext();

never got executed, so ctx is null. Hence the NullReferenceException.

It would be reasonable to log this as a bug, but it is a subtle one - and you wouldn't necessarily always want dragging the execution context to initialize the capture-contexts - it would have to be "if they are null".

like image 70
Marc Gravell Avatar answered May 10 '23 01:05

Marc Gravell