When we last reported on Simplified Parameter Null Validation, this feature was being challenged by several competing proposals ranging from attributes and compilers flags to full scale AOP with IL weaving. All of that has been discarded in favor of a narrowly tailored feature proposal which reduces the amount of code needed to validate non-null parameters to a single character.
The bang operator, !, can be positioned after any identifier in a parameter list and this will cause the C# compiler to emit standard null checking code for that parameter. For example:
void M(string name!) { ... }
Will be translated into:
void M(string name!) { if (name is null) { throw new ArgumentNullException(nameof(name)); } ... }
The rules are pretty straight forward.
- Null checks are injected at the beginning of the function before any other code and occur in the same order as the parameters in the function signature.
- Null checks specifically for reference equality to null, ignoring any
==
operator overloads. - If used in a constructor, the check will occur after the base class constructor is called.
- In the case of iterators, “null checking should always happen in initial call, not lowered methods.”
- A compiler error will occur if a null check appears on a parameter that cannot be null (e.g. struct, unmanaged).
- A compiler error will occur if a null check appears on a parameter without an associated implementation (e.g. abstract methods, delegates, interface methods, partial methods).
- A compiler error will occur if a null check is applied to an out parameter.
- A compiler warning will occur if a null check is applied to an explicitly nullable parameter (
string? x!
). Presumably this is to allow a subclass to be stricter than its base class. - A compiler warning will occur if a null check is applied to an optional parameter with a null default value.
Regarding the constructor rule, Microsoft did consider allowing the parameter check to occur before invoking the base class constructor. This has always been allowed by .NET and is arguably preferable. However, the C# syntax doesn’t expose this capability, and they didn’t want to generate code that couldn’t be created without using this feature.
In the test plan, we see these scenarios for generics:
void M<T>(T value!) { } is OK
void M<T>(T value!) where T : struct { } is an error
void M<T>(T value!) where T : unmanaged { } is an error
void M<T>(T value!) where T : notnull { } is OK
void M<T>(T value!) where T : class { } is OK
void M<T>(T value!) where T : SomeStruct { } is an error
void M<T>(T value!) where T : SomeClass { } is OK
One flaw in the design is that it offers no support for validating properties, as the value parameter is only implied. Orthoxerox suggested a work-around where the bang operator is applied to the set keyword in this scenario.
public Foo Foo {get; set!;}
public Bar Bar {
get { return bar; }
set! { bar = value; DoSomethingElse(); }
}