It’s Marching Season in Glasgow.
I’ve described some design requirements for implementing non-nullable and explicitly-nullable reference types in C#, and a design which meets those requirements.
However, there are two major items I’ve not yet discussed: how these null-aware types interact with .NET generic types, and how they interact with legacy code containing implicitly-null reference types.
In this episode, Generics:
In this series so far:
In this article we’ll digress about default values…
In my previous post, I outlined a list of requirements for non-nullable (and explicitly-nullable) reference types in C#. In this post we’ll dive into some further design decisions. Subsequent posts will look at the impact on generic types, plus backward-compatibility and some corner cases.
We add two new main concepts to the type system:
T!
’, andT?
’.It’s a truth widely acknowledged that null references in programming languages lead to faults. Newer languages, such as Apple’s Swift are controlling nulls, to avoid problems.
The .NET and C# design committees are looking at introducing non-nullable reference types in a future version of C#. This post outlines what I think are the requirements of (non-)nullable references in C#. A future post will describe a possible language design which fulfils them.
Existing languages, such as C# and Java, have a legacy of allowing any reference type to be null. This has several implications:
.NET 2.0 introduced the idea of nullable value types. This allows ints, doubles, bools, and other passed-by-value structures, to be marked as optional. C# uses the same terminology of ‘nullability’ to express this but ‘nullable’ value types are significantly different from nullable reference types:
Nullable<int>
’, which is usually abbreviated to ‘int?
’. Nullable value types are actually structures which contain a value type (or don’t, in the case of ‘null
’).NullReferenceException
. Nulls propagate through maths expressions, similarly to NULL values in SQL.It would be undeniably useful to allow reference types which exclude null. Such types might be annotated with an ‘!
’ after the type name.
This could help guarantee that the software never faults with a NullReferenceException
. It also makes something explicit in the language which is currently implicit, thus removing a documentation and coding burden. For example, annotating a method parameter as non-nullable would mean that there is no need to document that nulls are disallowed, and no need for the method to test ‘if (p == null) throw new ArgumentNullException("p");
’ on its first line.
However, that’s only a partial improvement on what we have now. There is an opportunity to also introduce:
System.Nullable<T>
’ would be able to accept any T regardless of whether T is a reference type or a value type.Why?
Explicitly-nullable reference types may require more explanation: after all, they seem not to add anything new to the language. Actually they add several things:
if (n.HasValue) DoSomethingWith(n.Value)
’. This removes ambiguity and one source of errors.What about type uniformity between nullable values and references? What does that add?
Uniformity simply makes writing type-generic code easier. It allows us to define, for example, an interface ‘IParser<T>
’ with method ‘T? Parse(string str)
’—regardless of whether T is a reference type or a value type.
In many cases the programmer should not have to care whether a type has value semantics or reference semantics (particularly if the type is immutable). By enforcing reference-value uniformity in nullable types, we remove a sometimes-artificial distinction between value types and reference types.
[Note that being able to use ‘Nullable<T>
’ for any type T implies that it would be possible to declare ‘Nullable<Nullable<T>>
’. This would rarely be required, but again, for reasons of uniformity, it would be a welcome addition to the language. At this point ‘Nullable<T>
’ is a misleading name, and ‘Optional<T>
’ would be a better name, but I suspect that that ship has already sailed.]
There is one other important requirement: interoperability.
It almost goes without saying that a new language feature must interoperate cleanly with legacy code, and not break source or binary compatibility. At a minimum:
string!
’ or a ‘string?
’ to a method which accepts an (implicitly-nullable) ‘string’. Similarly it must be possible to accept a non-null-aware reference result, and somehow massage it into a null-aware-reference form.string!
’ may have an ‘ArgumentNullException
’ thrown by the runtime system if it attempts to pass in a null
value. Similarly, old code must be able to accept a null-aware reference type result and use it as it would a non-null-aware result.?
’ and ‘!
’ annotations to method parameters and results, without breaking source or binary compatibility—even overriding base class non-null-aware methods with null-aware ones.These are my requirements. I’ll follow with another post soon, about a design for nullability in C# and .NET which fulfils these requirements.