At first glance, proposal #2145 seems like a logical extension to C# 8’s Nullable Reference Types feature. The basic idea is developers would no longer need to explicitly add argument null checks to methods that accept non-nullable parameters. However, this has become quite contentious.

This report seeks to explain the options and their pros and cons so the reader can make their own opinion. But before that, a quick note on why this is still important in C# 8.

Currently the Nullable Reference Types feature is merely informative. It will warn the developer about common mistakes when handling nulls, but only at compile time. When the application is running, all those compile-time checks don’t exist.

Furthermore, the compile checks don’t work at all when using reflection or dynamic.

Special Syntax: Bang Operator

The original proposal is to use the bang operator ! to indicate that the compiler should add an argument null check.

//typed code void Insert(string value!) { ... } //compiled code void Insert(string value) { if (value == null) throw new ArgumentNullException(nameof(value)); ... }

The argument for this option is it is minimally invasive. It would require only a minor change to the C# compiler and the new syntax is fully backwards compatible.

The arguments against this option are:

It is a new syntax for a very narrow use case.

It is easily overlooked when reading code.

It is easily forgotten.

It is redundant with the declaration that the parameter is non-nullable

Another issue is that value! would mean either “please check this for null” or “no need to check, I know it isn’t null” depending on the context. To solve this last concern, a variant of this proposal is to use a double-bang operator (string value!!).

New Attribute

Rather than a new syntax, another option would be just a new attribute that the compiler recognizes.

void Insert([NotNull] string value)

Attributes that affect compiled code aren’t a new thing for C#, so this would align with existing patterns. And if we ever get declarative parameter validation, it would look something like this.

The arguments against this option are:

It is very verbose compared to other options being considered

It is redundant with the declaration that the parameter is non-nullable

Compiler Flag

The next option being debated is a global compiler flag. When enabled, all non-nullable parameters would be checked.

The advantage of this option is you don’t have to think about it. Once enabled, the checks are added automatically so there is nothing to forget and no special syntax to learn.

The first argument against this is that there could be performance considerations. Proponents of the change argue the performance cost is insignificant, the feature could be optionally applied only to public methods, and that null checks happen anyways for any method invocation.

Another argument against this feature is the developer might want to throw a different exception. The counter to this is that they shouldn’t throw anything besides an ArgumentNullException . Also, a compiler directive could disable the feature for just one file or method when special handling is needed.

The final argument has the most weight behind it. This would only be the second time a compiler flag changes the semantics of the code. Compiler flags such as 'nullable' don’t actually change how the code behaves, it is only a compile-time feature.

The exception to this rule is the 'checked' compiler flag, which changes how integer overflows behave. Among the C# language designers, it is considered a mistake because you can’t tell how a given piece of code will operate without knowing how that flag is set at the compiler level.

The counter-argument doesn’t refute this, but maintains the change is a necessary step to bring the Nullable Reference Types feature closer to completion. To which some maintain that NRT was never meant to be a complete solution and for the sake of backwards compatibility it shouldn’t affect runtime behavior.

External AOP and IL Weaving

The term “IL weaving” refers to a post-processing step that modifies the assembly after the compiler is done. This is used in aspect-oriented programming tools such as PostSharp and the canceled Code Contracts project.

The specific tool mentioned in the debate is Fody NullGuard. Fody is a MIT-licensed IL weaver built on top of Mono.Cecil.

The argument against IL weaving is it requires 3rd party tools, doesn’t work well with static analysis tools that run in the IDE, breaks Edit-and-Continue, and slows down builds.

Internal AOP or Macros

There is some talk about having some kind of internal AOP or macro system. This would allow developers to extend the language themselves, rather than waiting on C# enhancements.

At this time this option hasn’t gained much traction. An internal AOP or macro system would be a major change to the entire tool chain. Furthermore, it would effectively allow developers to create their own dialects of C#, fragmenting the language.

Do Nothing

The final option is to simply do nothing. The strongest argument for this is stance is this is merely a “quality of life” feature that doesn’t offer anything new to the developer. And while reducing boilerplate is always appreciated, the negative aspects of each of the other proposals outweigh the benefits.

Furthermore, only a small amount of code is needed for the null check in any given function.

The counter-argument is this is one of the most common examples of boilerplate code in C# and is frequently missing, both in examples and in production code. To which the response is that static analysis tools will detect most, though not all, occurrences where null checks are missing. And with NRT enabled, static analysis checks can become more accurate.