The null-safe dereferencing operator stops evaluation of the whole expression as soon as it hits null. So if deserializedBody is null, it won't try to evaluate date, and it won't call ToString() on anything - so you won't get an exception.
Complete example:
using System;
class Test
{
DateTime date = DateTime.UtcNow;
static void Main()
{
Test t = null;
// No exception thrown
Console.WriteLine(t?.date.ToString());
}
}
Here the expression t?.date.ToString() is equivalent to:
t is null ? null : t.date.ToString()
(except that t is only evaluated once). It's not equivalent to
(t is null ? null : t.date).ToString()
... which is what I suspect you were expecting it to do.
But no, this doesn't protect against the situation where deserializedBody is non-null, but deserializedBody.date is null.
If you were expecting a warning due to C# 8 nullable reference types, I believe dynamic expressions are never null-checked. As an example of this, consider:
class Test
{
public string? foo;
}
class Program
{
static void Main(string[] args)
{
Test t = new Test();
dynamic d = t;
Console.WriteLine(t.foo.Length); // Warning
Console.WriteLine(d.foo.Length); // No warning
Console.WriteLine(d.bar); // No warning for this either
}
}
I suspect the reason for this is that the compiler basically has no information about what d.foo is. For every case where there's a useful warning, there'd be another case where the warning isn't useful. When you're in dynamic typing territory, you've already accepted a certain degree of risk - it would seem odd to warn that dereferencing d.foo is risky when the access of d.foo is risky to start with.