6

I've started using nullable reference types in C# 8. So far, I'm loving the improvement except for one small thing.

I'm migrating an old code base, and it's filled with a lot of redundant or unreachable code, something like:

void Blah(SomeClass a) {
  if (a == null) {
    // this should be unreachable, since a is not nullable
  }
}

Unfortunately, I don't see any warning settings that can flag this code for me! Was this an oversight by Microsoft, or am I missing something?

I also use ReSharper, but none of its warning settings appear to capture this either. Has anybody else found a solution to this?

Edit: I'm aware that technically this is still reachable because the nullability checks aren't bulletproof. That's not really the point. In a situation like this, where I declare a paramater as NOT nullable, it is a usually a mistake to check if it's null. In the rare event that null gets passed in as a non-nullable type, I'd prefer to see the NullReferenceException and track down the offending code that passed in null by mistake.

Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
Adam Wall
  • 163
  • 8
  • 4
    Nullability of reference types is a compile-time check, but it's still possible to pass null from code that doesn't have nullable reference types enabled, so that branch of execution is still reachable. – madreflection Apr 02 '20 at 20:35
  • @madreflection I'm aware that technically it's still reachable, but 99% of the time I don't care about that, I would like to see a NullReferenceException if somebody passed in null as a non-nullable parameter. I want my code to assume that a NonNull parameter is, well, NOT null. – Adam Wall Apr 02 '20 at 20:42
  • 1
    I am not sure if the sonarlint extension for static code analysis already has a rule like that implemented, if not it might be included in the future (you could potentially also write your own validation rule) – CitrusO2 Apr 02 '20 at 22:34
  • 2
    Such null checks are definitely useful in public APIs, as you don't know whether the caller is honoring the nullability annotations. But in private layers, it is reasonable to remove them in many cases (some cases might still prefer defense in depth). This is not something the C# compiler aims to help with, but seems a perfect task for an analyzer. – Julien Couvreur Apr 03 '20 at 14:52
  • @JulienCouvreur I agree completely. In my case, the code is private, so I own both the caller and callee. Since I treat all warnings as errors, and the callee is also undergoing nullability analysis, I am reasonably certain that I can remove the "== null" checks. If the callee were to incorrectly send in null, that is a programming error that the callee must fix: I wouldn't mind if a NullReferenceException was thrown as a result of this. – Adam Wall Apr 03 '20 at 22:58

1 Answers1

6

It's really important to note that not only are the nullability checks not bullet proof, but while they're designed to discourage callers from sending null references, they do nothing to prevent it. Code can still compile that sends a null to this method, and there isn't any runtime validation of the parameter values themselves.

If you’re certain that all callers will be using C# 8’s nullability context—e.g., this is an internal method—and you’re really diligent about resolving all warnings from Roslyn’s static flow analysis (e.g., you’ve configured your build server to treat them as errors) then you’re correct that these null checks are redundant.

As noted in the migration guide, however, any external code that isn’t using C# nullability context will be completely oblivious to this:

The new syntax doesn't provide runtime checking. External code might circumvent the compiler's flow analysis.

Given that, it’s generally considered a best practice to continue to provide guard clauses and other nullability checks in any public or protected members.

In fact, if you use Microsoft’s Code Analysis package—which I’d recommend—it will warn you to use a guard clause in this exact situation. They considered removing this for code in C# 8’s nullability context, but decided to maintain it due to the above concerns.

When you get these warnings from Code Analysis, you can wrap your code in a null check, as you've done here. But you can also throw an exception. In fact, you could throw another NullReferenceException—though that's definitely not recommended. In a case like this, you should instead throw an ArgumentNullException, and pass the name of the parameter to the constructor:

void Blah(SomeClass a) {
  if (a == null) {
    throw new ArgumentNullException(nameof(a));
  }
  …
}

This is much preferred over throwing a NullReferenceException at the source because it communicates to callers what they can do to avoid this scenario by explicitly naming the exact parameter (in this case) that was passed as a null. That's more useful than just getting a NullReferenceException—and, possibly a reference to your internal code—where the exception occurred.

Critically, this exception isn't meant to help you debug your code—that's what Code Analysis is doing for you. Instead, it's demonstrating that you've already identified the potential dereference of a null value, and you've accounted for it at the source.

Note: These guard clauses can add a lot of clutter to your code. My preference is to create a reusable internal utility that handles this via a single line. Alternatively, a single-line shorthand for the above code is:

void Blah(SomeClass a) {
  _ = a?? throw new ArgumentNullException(nameof(a));
}

This is a really roundabout way of answering your original question, which is how to detect the presence of null checks made unnecessary by C#’s non-nullable reference types.

The short answer is that you can’t; at this point, Roselyn’s static flow analysis is focused on identifying the possibility of dereferencing null objects, not detecting potentially extraneous checks.

The long answer, though, as outlined above, is that you shouldn’t; until Microsoft adds runtime validation, or mandates the nullability context, those null checks continue to provide value.

Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
  • Thanks. Unfortunately, this is what I feared. There is no warning for checking what I want to check (even though it would seem trivial to implement). It appears the designers of this feature are not trying to accommodate developers like me... So, I will have to manually search all cases of "== null" to remove the redundant overly-defensive code. – Adam Wall Apr 03 '20 at 00:34
  • 1
    @AdamWall: Something about my answer was nagging at me, and after thinking about it I realized that I was conflating warnings from Microsoft’s Code Analysis with warnings from Roslyn’s static flow analysis As a result, my original reply was likely talking past you, and explaining things you were well aware of. I sincerely apologize for that. I’ve updated my answer to differentiate between these two warnings—and to directly answer your question. But I’ve also kept the original guidance as (I hope!) it will be useful for other readers starting out with non-nullable reference types. – Jeremy Caney Apr 03 '20 at 05:15
  • No offense taken, I realize my question was perhaps a little terse. I am indeed looking to remove those "== null" checks, as I own both the caller and callee, so I can rely on people not sending in null parameters, as I treat all those warnings as errors. – Adam Wall Apr 03 '20 at 22:55
  • Aside, readers interested in this thread may also be interested in [a proposal to add runtime validation of parameters to C#](https://github.com/dotnet/csharplang/issues/2145). This may or may not end up being accompanied by detection of extraneous checks in Code Analysis. But it would at least greatly reduce the amount of code required to _ensure_ null checks, and satisfy the compiler that nulls are accounted for. I’ve also updated the answer with a link to this proposal. – Jeremy Caney Jun 17 '20 at 17:22