C# Futures: Nullability Tracking
Probably the most common error type in .NET is the Null Reference Exception. The root cause of this error is C#’s inability to express the concept of non-nullable references, which in turn makes compiler-enforced null checking it too burdensome.
To address this problem, there is a proposal for mandatory and explicitly nullable references. A nullable reference would be declared by appending a ? symbol just like nullable value types. A mandatory reference or non-nullable reference would be declared with the ! suffix.
Both mandatory and nullable references would be considered language-only concepts. They would change the behavior of the compiler but not the resulting IL code.
Nullable references would require explicit null checks before the compiler would allow accessing any methods or properties on the object. A null check would also be needed before casting a nullable reference into a mandatory reference.
Mandatory references require that the compiler prove the value contained is cannot be null. Since this is a compiler-only precept, this guarantee doesn’t apply to scenarios such as deserialization.
Not that when reading the full proposal, you will encounter the term “general reference”. This refers to normal references in C# that are neither mandatory nor explicitly nullable. Since such references are considered to be legacy, the AllowGeneralReferences attribute can be used to tell the compiler to not allow them.
When combined with implicitly declared variables, the ! or ? suffix would be applied to the var keyword.
Under the current proposal, you can implicitly convert from mandatory to nullable references. However, it will not allow you to implicitly convert from a general to a nullable reference. The explanation is,
makes an assumption about the intent of the general reference (maybe it is conceptually mandatory, rather than conceptually nullable as assumed here).
This doesn’t make much sense. If an actually mandatory reference can be implicitly converted into a nullable reference, then a “conceptually mandatory” one should be as well.
Conversions from mandatory or nullable references back to general references are likewise not allowed for questionable reasons.
An ongoing problem with non-nullable reference type proposals is the fact that they are very difficult to enforce in the constructor. One proposal to solve this is to simply not solve it. Like ‘readonly’ fields that can be assigned to more than once in a constructor, we would simply say that the rule isn’t inforce during construction and the developer needs to be careful.
This may seem counter-productive at first, but it actually makes a lot of sense. Developers already have to be careful about accessing potentially uninitialized fields in constructors. This wouldn’t change that fact; it would simply fail to reduce the risk of null reference exceptions in this particular case.
Mixing New and Legacy Code
Another issue that non-nullable reference proposals must address is how to deal with legacy code. Say, for example, the Base Class Library is updated to use the new syntax. Existing applications may not be able to make the conversion right away, so there needs to be a way to turn it off. This proposal offers the IgnoreNewStyleReferences attribute to selectively ignore the new style of references for a given external assembly.
More on Nullable References
Earlier in this article we mentioned the two states a nullable reference could be in: unknown or proven to be non-null. This proposal goes on to say that if the reference is proven to be null, it cannot be used at all. Instead, developers must use a hard-code null, which is makes intent clearer.
There are many other issues that will need to be addressed. For example, will it be possible to have arrays of mandatory references? How will mandatory/nullable be exposed via reflection? How will this interact with generics?