C# Futures: Nullable Reference Types
No, the headline isn’t a typo. One of the new proposals for C# is to assume that all reference variables are non-nullable by default. Under the new syntax, you would need to explicitly indicate when a reference variable is nullable, just as you do for value types.
As with value types,
T would refer to a non-nullable type and
T? would refer to a nullable type. Warnings would be generated under the following circumstances:
- The nullable variable is dereferenced
- A nullable variable or parameter is assigned to a non-nullable variable
- Casting from
- Casting from
- Assigning a null literal to a non-nullable variable or parameter
- The constructor doesn’t assign a value to all non-nullable fields
For the first two cases, the warning will be omitted if you use the bang operator (
x!) or if the compiler can prove that a null-check has already been performed.
Downlevel compilers will ignore nullability annotations, so that isn’t an issue. There will, however, have to be some sort of marker at the assembly level to indicate that the library was compiled with nullability annotations enabled.
Since all of this nullability stuff is technically a breaking change, the current plan is to allow developers to opt-in to the following categories:
- Nullable warnings
- Non-null warnings
- Warnings from annotations in other files
The proposal continues:
The granularity of the opt-in suggests an analyzer-like model, where swaths of code can opt in and out with pragmas and severity levels can be chosen by the user. Additionally, per-library options ("ignore the annotations from JSON.NET until I'm ready to deal with the fall out") may be expressible in code as attributes.
This design is predicated based on trying to achieve these three goals:
- Users can adopt nullability checking gradually as they want to
- Library authors can add nullability annotations without fear of breaking customers
- Despite these, there is not a sense of "configuration nightmare"
You will not be able to have a nullable and a non-nullable overload of the same method. While technically the CLR supports this, it is not part of the CLS or Common Language Specification. This means most compilers would not understand what’s happening. HaloFour explains:
modreq is not-CLS. modopt does allow for overloads, but requires specific understanding on the part of all consuming compilers since the modifier must be at the very least copied into the call signature. Both break compatibility with existing method signatures. For something that will hopefully proliferate across the entire BCL very quickly, using modopt would create a big hurdle.
Additional warnings could appear when working with generics:
- Casting from
C<T?>, unless the type parameter is covariant (out)
- Casting from
C<T>, unless the type parameter is contravariant (in)
C<T?>when then type parameter is constrained to be non-null
class” would constrain the generic to be non-null. Using “
class?” would allow for nulls. The proposal continues:
If a type parameter is unconstrained or has only nullable constraints, the situation is a little more complex; this means that the corresponding type argument could be either nullable or non-nullable. The safe thing to do in that situation is to treat the type parameter as both nullable and non-nullable, giving warnings when either is violated.
Arrays pose a special challenge, as it wouldn’t necessarily be possible to ensure that every slot in a non-nullable array has a value.
We cannot adequately track that all elements of an array of non-null references are initialized. However, we could issue a warning if no element of a newly created array is assigned to before the array is read from or passed on. That might handle the common case without being too noisy.
Open Design Questions
default(T) be considered a warning? Or is it assumed to return
T? instead of
? be omitted on local variables, with nullability inferred based on usage?
Can parameters auto-generate the null checks using
T! x pattern?
Can nullable value types be tweaked so that you can write
x.method instead of
x.value.method (this would apply when x is known to be non-null such as after a successful null check)?
Sentence difficult to read