Curiosity is bliss    Archive    Feed    About    Search

Julien Couvreur's programming blog and more

Analyzing a nullability example

 

Cezary Piątek posted a good overview of the C# nullable reference types feature. It includes a critique of a code snippet. Examining that snippet is a good way to understand some of the C# LDM’s decisions. In the following, Cezary expects a warning on a seemingly unreachable branch and no warning on the dereference.

#nullable enable

public class User 
{
    public static void Method(User userEntity)
    {
        if (userEntity == null) // Actual: no warning for seemingly unreachable branch. 
        {
        }
        
        var s = userEntity.ToString(); // Actual: warning CS8602: Dereference of a possibly null reference.
    }
}

sharplab

Why is there no warning on the seemingly unnecessary null test if (userEntity == null) ... or the apparently unreachable branch?

It’s because such tests are useful and encouraged in public APIs. Users should check inputs and the compiler should not get in the way of good practices. The branch of the if is therefore reachable.

Then, what is the state of userEntity within the if block?

We take the user’s null test seriously by considering userEntity to be maybe-null within the if block. So if the user did userEntity.ToString() inside the if, the compiler would rightly warn. This protects the user against a null reference exception that could realistically happen.

Given those, what should be the state of the userEntity at the exit of the if?

Because we’re merging branches where userEntity is maybe-null (when the condition of the if is true) and not-null (in the alternative), the state of userEntity is maybe-null. Therefore we warn on dereference on userEntity after the if. Note that if the if block contained a throw, the userEntity would be considered not-null after the if. This is a common patter: if (userEntity is null) throw new ArgumentNullException(nameof(userEntity));.

comments powered by Disqus