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.