Search code examples
c#-8.0nullable-reference-types

Can this value really ever be null, or is the C# compiler just confused by code?


I've found some code that runs afoul of the new nullable reference types in C# 8, saying i could be referencing a null, when i cannot figure out how it could possibly ever be null:

https://dotnetfiddle.net/AoEMzp

public static DateTime JoinNullableDateTime(DateTime? date, DateTime? time)
{
   if ((date == null) && (time == null))
      return DateTime.MinValue;
   else if ((date != null) && (time == null))
      return date.Value.Date;
   else if ((date == null) && (time != null))
      return DateTime.MinValue.Add(time.Value.TimeOfDay);
   else
      return date.Value.Date.Add(time.Value.TimeOfDay);
}

Both Visual Studio and .NET Fiddle return the warnings for the last line:

return date.Value.Date.Add(time.Value.TimeOfDay);

.NET Fiddle

enter image description here

Visual Studio

enter image description here

So my question is, can anyone give any examples of values for date and time that would cause a NullReferenceException in this code?

At the start, the possible combinations of null-ness are:

date time Possible?
null null Yes
not-null null Yes
null not-null Yes
not-null not-null Yes
if ((date == null) && (time == null))`
   return DateTime.MinValue;

At this point the possible combinations of null-ness are:

date time Possible?
null null No (because we returned)
not-null null Yes
null not-null Yes
not-null not-null Yes
else if ((date != null) && (time == null))
   return date.Value.Date;

At this point the possible combinations of null-ness are:

date time Possible?
null null No (because we returned)
not-null null No (because we returned)
null not-null Yes
not-null not-null Yes
else if ((date == null) && (time != null))
   return DateTime.MinValue.Add(time.Value.TimeOfDay);

Finally the possible combinations of null-ness are:

date time Possible?
null null No (because we returned)
not-null null No (because we returned)
null not-null No (because we returned)
not-null not-null Yes

So at this point it is not possible for date nor time to be null. Both are guaranteed to be NOT NULL.

else
   return date.Value.Date.Add(time.Value.TimeOfDay);

I cannot see any way in which either date or time could be null on the final line - yet there it is:

CS8629 Nullable value type may be null.

So what am i missing?

In fact the nulls go away even faster

After the first line

(date == null) && (time == null)

We know that it is still possible for one of them to be null - but not both. So then we hit the 2nd check:

(date != null) && (time == null)

Before this line we knew that only one was null. And now we've found the one that it was: it was time.

This means by the 3rd line it is impossible for date to be null. And yet i added a check anyway, because the compiler told me to, and the compiler is infallible. But i have the check anyway:

(date == null) && (time != null)

So the extra checking even on the third if was redundant. And certainly it is redundant on the 4th if. And yet there is the compiler.

What am i doing wrong?

I'm certain i could re-write the function into something harder to understand, so it doesn't confuse Roslyn. But this isn't about this function, it's about the pattern being used - and if this pattern has lurking NullReferenceExceptions: i want to know about it!

Bonus

I cross-checked with .NET Fiddle in case it's an artifact in my Visual Studio.


Solution

  • Probably the code analyzer is not sufficiently intelligent to conclude your date and time can never be null.

    You can inform it of that fact by using the 'null forgiving' operator:

    public static DateTime JoinNullableDateTime(DateTime? date, DateTime? time)
    {
       ...
       else
          return date!.Value.Date.Add(time!.Value.TimeOfDay);
    }