Quick Links

An early preview of select features to be included in this year's C# 11 release is now available as part of Visual Studio 2022 Update 1. The most contentious addition is integrated parameter null-checking support. This uses code generation to eliminate manual comparisons against null.

In this article we'll explore the current state of the feature, the criticism leveled against it, and how it could change before the final release. The syntax and behaviors described here apply to the current (March 2022) C# 11 preview and could change considerably before launch.

What Is Parameter Null-Checking?

First let's clarify what we're talking about. C# parameters without nullable reference types accept null as a value irrespective of their actual type:

private string hello(string name) {
    

return "Hello " + name;

}

// This works

hello(null);

This behavior is often undesirable. In the example above, passing null would produce nonsensical output. To overcome this issue, it's common for developers to add their own null-checks at the top of methods:

private string hello(string name) {
    

if (name is null) {

throw new ArgumentNullException(nameof(name));

}

return "Hello " + name;

}

// ArgumentNullException

hello(null);

This can create a lot of boilerplate code when a method has several non-nullable parameters.

Parameter Null-Checking Syntax In C# 11

C# 11 lets you condense the above example to the following code:

private string hello(string name!!) {
    

return "Hello " + name;

}

// ArgumentNullException

hello(null);

The special !! syntax after the parameter name has the same effect as a manual

        if (... is null)
    

/

        throw new ArgumentNullException(...)
    

block. An ArgumentNullException instance will be thrown if you pass null to a parameter that uses the syntax.

When multiple parameters are marked as non-nullable, checks are executed in the as-written parameter order. The thrown exception will reference the first non-nullable parameter to receive null in the invocation.

The syntax can be used with most kinds of parameter including those in

        async
    

methods, iterators, and lambdas. It doesn't work with abstract method parameters,

        extern
    

and delegate parameters, and interface methods that aren't a Default Interface Method (DIM).

How Does It Work?

The new syntax is based on compiler code generation. The compiler creates the familiar

        if (... is null)
    

/

        throw new ArgumentNullException(...)
    

code as it processes your source files.

The feature's not adding anything to C#'s language capabilities. It's merely writing common userland code for you, offering a concise syntax that cuts down on repetitive source sections.

The generated code is positioned at the top of each affected method. If the method is a constructor, the code will be executed ahead of class field initializations and any calls to the

        this
    

and

        base
    

constructors. This is the only practical difference compared to manually created null-check expressions.

Why Is It Controversial?

Critics of the proposal are concerned about the syntax (!! poorly communicates its meaning and effect), its presence on the parameter name instead of the type, and whether boilerplate code generation is within the remit of the C# language project. There are also debates over null checking's interactions with nullable reference types.

Let's unpick some of these arguments and their implications.

!! Exists On The Parameter Name, Not The Type

Writing

        string param!!
    

feels unnatural because you're apparently imbuing the parameter name with extra behavior, rather than the type. Yet this is entirely consistent with what's actually happening.

Type checking is a compiler-level activity; parameter null checking generates runtime code that checks the value of the parameter. Hence it makes sense for the syntax to target the parameter instead of its type.

Parameter Names Shouldn't Surface Implementation Details

Another complaint is that param!! communicates information about the method's implementation via its declaration. The syntax does not affect the method's signature, instead revealing aspects of its internals. This is unsatisfactory to many developers.

One floated way of addressing the issue would be to provide parameter null-checking behavior as an attribute. This isn't what attributes are for though - they're plain metadata whereas null-checks generate runtime code. There is also precedent for the behavior of !! in other method declaration components: take

        async
    

, which reveals its target to be asynchronous, and wires up an appropriate execution model for you.

Compatibility With Null Reference Types

Parameter null checks and null reference types (NRTs) are compatible with each other. However you will see new compiler warnings if you combine !! with a nullable type:

private string hello(string? name!!) {
    

// ...

}

In this example the string? type explicitly allows null while

        name!!
    

rejects it at runtime. The compiler detects this contradictory condition and issues a warning:

CS8995 Nullable type 'string?' is null-checked and will throw if null.

Whether the two mechanisms are conceptually compatible is a different problem. Nullable reference types enforce a value can't be null at compile time. Parameter null-checks throw an exception when a value is null at runtime. In an ideal landscape, only the former would be necessary - all methods would use NRTs to indicate whether they can accept null and enforcement would be wholly handled by the compiler.

Unfortunately the C# landscape isn't ideal. Nullable reference types are an opt-in feature which have not been universally adopted. It's not realistic to expect all existing code accumulated over the 20 years of the C# language to support NRTs. Parameter null-checks are a pragmatic way of bridging new code that predominantly uses NRTs with older libraries that may return null at runtime. They make sense where your methods could be handed null - so an NRT can't be used - by code that hasn't adopted the modern approach.

It's a mistake to approach param!! as a way of signalling that the parameter shouldn't be passed null - that's the requirement that string? caters for. The presence of !! is only known to the compiler. It's there to raise an ArgumentNullException on your behalf.

Code Generation Might Be A Bad Idea

There are some views that automatically generating code to perform this function might be more trouble than it's worth. Although it could remove a lot of boilerplate, it may also cause unexpected behaviors and make it harder to reason about the program.

Developers need to understand that ArgumentNullException instances can be thrown wherever !! is used. The exception is inflexible and cannot be changed, even in situations such as instance members where a NullReferenceException may be more appropriate. This has led some commenters to suggest that parameter null-checks in their current form shouldn't become part of C#.

There's no direct answer to this problem. Evidently the C# development team has decided code generation will have a net positive benefit on the community by saving time and reducing repetitiveness. However it should be acknowledged that null-checks result in one-size-fits-all code being added to your project, creating the possibility of problems if that size is later proved incorrect.

Summary

Two characters have created a discussion that's still unfolding: as it stands, param!! is coming to C# as a way to generate throw ArgumentNullException(...) for you. Despite its potential for slimming down boilerplate code - over 20,000 explicit null-checks have been removed from .NET Runtime using the syntax - there remain a number of lingering doubts and concerns.

We've dissected the most prominent of these above and seen that most of the complaints are unfounded and based on misunderstandings. Arguably the biggest criticism surrounds the syntax itself, with !! viewed as opaque, difficult to discover, and inaccessible to newcomers. There are various proposals to replace it with something more obvious; string param notnull is gaining considerable attention, so the final form may look different when C# 11 arrives.

You can have your say on the proposal by contributing to its discussion thread on GitHub. If you want to try out the current preview for yourself, you'll need Visual Studio 17.1 (2022 Update 1) and .NET SDK 6.0.200. You'll then need to change LangVersion to preview within your .csproj file to enable C# 11 support.