C# Null; Nullable; Null Operators; Null-Coalescing – Part 1

Given the above title, you may be expecting nothing (null) to follow.  Following a couple of years break with the language I have recently been working in C# again and getting myself up to speed with how operating with null has improved since version 7.0.  I am pleased with how the language has developed to include a better toolset and compiler warnings.  Thus, assisting development in producing null-safe[r] code with a more robust approach.  I quite like the null-coalescing assignment operator, although its name is a bit of a mouthful.

Is this Null-Safe?

There are a number of potential pitfalls here.  Nullable reference types abound.  It is not null-safe.  However, let’s try to assume that value clientReference passed to WriteName will always be a valid value and that ReadClientRecord will always return a populated record.  Or at least, let’s assume that these assumptions were the ones made by the developer at the time they wrote this code.

From development through to production, the above snippet may work without any apparent issue. It may work for many months or years. But then – suddenly – boom, your application stops working. 

Amongst many concerns with the above code is Client c =Client is a reference type that is returned by ReadClientRecord.  Reference types, by default, can be set to a value or be null.  They are “nullable“.  If for whatever reason, ReadClientRecord, returns null then the next line in our code will error with “Object reference not set to an instance of an object”. 

We now, potentially, have a broken product affecting users and their ability to perform their functions.  For the developer who wrote the code, which could be years ago, this may not be of any concern but for someone else, it could be affecting their ability to fulfil an order and be disastrous for their business. 

I will be going into more detail in Part 2 of this article, but first, let’s look at the history of null referencing and the toolset C# now provides for using and handling nulls.

Null Reference Types

Sir Charles Antony Richard Hoare introduced Null-References in ALGOL W back in 1965.  Null References were introduced because: –

simply because it was so easy to implement

he also added…

[it was] my billion-dollar mistake

However, the reality was more complicated than that. You can watch Tony Hoare giving a fascinating talk about the origins of null at QCon London in 2009.  The video is well worth watching.  I like one of Tony’s early asides “IBM could not afford 39 bits in each word so they only had 36 bits”.  Those were the days!! 

At around the 26 mins-in, Tony Hoare talks about how null was used as a simple workaround to resolve issues with uninitialized variables and how a null pointer was proposed as a possible value of every reference variable.  Further on, at the 37 mins, Tony Hoare asks and answers, what is currently the driving factor behind languages becoming null-safe?  Watch the video to find out Tony’s thoughts on this.  It is a fascinating presentation.

Evolving Null in C#

Historically many languages have lacked good Null-Safety features.  Some newer languages, for example, Rust, are designed from the ground up not to rely on null.  Thus forcing the developer to explicitly code and cater for instances when they need to determine the difference between when something is set or not. The Rust language provides a standard way of doing this via an Option enum that encodes the concept of a value type that can be set to something or nothing.

Over the years, some languages have introduced better null-safety and C# is no different – especially with its introduction of non-nullable reference types in 8.0. 

Here is a list that I have collated relating to the evolution of C# and the use of null: –

  • C# 2.0 – September 2005

    • Nullable Value Types

      Introduced in C# 2.0 the unary postfix ? operator allows value types to be nullable.

      int? b = 10; if (b.HasValue) { Console.WriteLine($"b is {b.Value}"); } else { Console.WriteLine("b does not have a value"); }

  • C# 6.0 – July 2016

    • Null-conditional operators

      C# 6.0 introduced ?. and ?[] operators for when accessing members ?. and elements ?[].

      If one operation in a chain of conditional member or element access operations returns null, the rest of the chain does not execute.

      In the example below, if Person object is null then the method OutputDetails is not called.

      var Person = FindMe(); Person?.OutputDetails();
  • C# 7.0 – March 2017

    • Null-coalescing operator

      C# 7.0 introduced the ?? operator.  If the expression on the left is null then the value of the right is evaluated and used.

      int? a = null;<br>int b = a ?? -1; Console.WriteLine(b); // output: -1

      The ?? operator does not evaluate its right-hand operand if the left-hand operand evaluates to a non-null value.

    • is null

      C# 7.0 introduced the pattern matching for the is and switch keywords.  Constant pattern matching allows evaluation against a specified constant value.  And thus, is null was born:

      if ( myVar is null ) { }

      The expression x is null is computed differently for reference types and nullable value types. For nullable value types, it uses Nullable<T>.HasValue. For reference types, it uses (object)x == null.

  • C# 8.0  – September 2019

    • Nullable reference types

      C# 8.0 Introduced the ability to enable the mandatory explicit declaration reference types that can be nullable.  Historically, reference types are nullable by default.  This feature allows you to turn that on its head.  However, it is not enabled by default as it could result in issues with legacy code due to additional compiler warnings.  I cover more on Nullable Reference Types in Part 2 of this series.

    • Null-forgiving operator

      The single ! postfix operator has no effect at runtime.  It is used by the compiler’s static flow analysis.  Useful if you know that the expression cannot but null and you wish the compiler’s analysis not to warn you about a null reference.  However, if you are wrong and suppress the warning, you could have issues later down the line.

      Person? p = Find("John"); if (IsValid(p)) { Console.WriteLine($"Found {p!.Name}"); }

    • Null-coalescing assignment operator

      Introduction of the ??= operator.  This allows the value on the right-hand side to be assigned to the left-hand side only if the left-hand side evaluates to null.

      int? i = null; numbers.Add(i ??= 17); Console.WriteLine(i); // output: 17

      The ??= operator doesn’t evaluate its right-hand operand if the left-hand operand evaluates to non-null.

  • C# 9.0 – September 2020

    • is not null

      Introduced for comparing null values. 

      If ( myString is not null ) { ... }

      Given something so simple it has taken some time for this to be introduced in C# 9.0 considering is null was introduced in #7.0.

    • Ability to apply nullable attribute annotations to local functions

      bool IsValid(<strong>[NotNullWhen(true)]</strong> string? line) { return !string.IsNullOrEmpty(line) && line.Length >= mark.Length }

Leave a Reply

Your email address will not be published. Required fields are marked *