C# 8 & No More NullReferenceExceptions – What about legacy code?

A .NET guideline specifies that an application should never throw a NullReferenceException. However, many applications and libraries do. The NullReferenceException is the most common exception happening. That’s why C# 8 tries to get rid of it. With C# 8, reference types are not null be default. This is a big change, and a great feature. However, what about all the legacy code? Can old libraries be used with C# 8 applications, and can C# 7 applications make use of C# 8 libraries?

This article demonstrates how C# 8 allows mixing old and new assemblies.

Old and new

Why avoiding NullReferenceException?

When a NullReferenceException occurs, the reason often is not easy to find. Errors occur at places far away from the real issue. That’s why applications should not throw NullReferenceException, and instead check for null values, and throw a ArgumentNullException. If a null value is passed with an argument, at method entry it can be checked to not accept the null value. Throwing an ArgumentNullException here, it’s easy to detect where the problem is.

See the guidelines on throwing standard exception types

Let’s see what C# 8 does to avoid the NullReferenceException.

Install C# 8

At the time of this writing, C# 8 is not yet released. However, you can try it out. At the time of this writing, to try nullable reference types, Visual Studio 2017 15.5-15.7 is needed – and the preview of C# Nullable Reference Types. You can easily install it, and uninstall it as described in the linked article.

Installing this version of the compiler, you’ll get many warnings on many of your existing C# projects. By default, the latest major version of the C# compiler is used. To get rid of the warnings, you can explicitly set the C# compiler version to an earlier version with your existing projects, or you can also uninstall the C# 8 compiler again.

Reference Types are not Nullable

The new nullability is easy to understand. The syntax is similar to nullable value types. Similar to value types, if the ? is not specified with the declaration of the type, null is not allowed:


int i1 = 4; // null is not allowed
int? i2 = null; // null is allowed
string s1 = "a string"; // null is not allowed
string? s2 = null; // a nullable string

view raw

Nullability.cs

hosted with ❤ by GitHub

While the syntax with value types and reference types now looks similar, the functionality behind the scenes is very different.

  • With value types, the C# compiler makes use of the type Nullable. This type is a value type and adds a Boolean field to define if the value type is null or not null.
  • With reference types, the C# compiler adds the Nullable attribute. Version 8 of the compiler knows about this attribute and behaves accordingly. C# 7 and older versions of C# don’t know about this attribute, and just ignore it.

Compiling a program with C# 8, both Book b and Book? b becomes Book b with C# 7.

The following Book class defines the non-nullable properties Title and Publisher, and the nullable property Isbn. In addition to that, this type contains a constructor making use of C# 7 tuples and deconstruction. Using the Book type and accessing the Isbn property, a value can only be written to a variable of type string?. Assigning it to string results in the C# compilation warning converting null literal or possible null value to non-nullable type.


class Book
{
public string Title { get; }
public string Publisher { get; }
public string? Isbn { get; }
public Book(string title, string publisher, string? isbn)
=> (Title, Publisher, Isbn) = (title, publisher, isbn);
public Book(string title, string publisher)
: this(title, publisher, null) { }
public void Deconstruct(out string title, out string publisher, out string? isbn)
=> (title, publisher, isbn) = (Title, Publisher, Isbn);
public override string ToString() => Title;
}

view raw

Book.cs

hosted with ❤ by GitHub


var book = new Book("Professional C# 8", "Wrox Press");
// string isbn = book.Isbn; // error: converting null literal or possible null value to non-nullable type
string? isbn = book.Isbn;

view raw

Program.cs

hosted with ❤ by GitHub

Assigning Nullable to Non-Nullable

In case you need to assign a nullable type (like the Isbn property from the Book class), C# 8 analyzes the code. In the code snippet, because the isbn is compared to null, after the if statement, isbn cannot be null anymore. After the if statement it’s ok to return the isbn variable of type string? although the method is declared to return the non-nullable string:


static string GetIsbn1(Book book)
{
string? isbn = book.Isbn;
if (isbn == null)
{
return string.Empty;
}
return isbn;
}

view raw

Program.cs

hosted with ❤ by GitHub

Of course, you can also use the coalescing operator instead. This also allows to use a simple Lambda with the implementation of the method:


public string GetIsbn2(Book book)
=> book.Isbn ?? string.Empty;

view raw

Program.cs

hosted with ❤ by GitHub

Returning from and passing to methods

The class NewAndGlory is defined in a class library built with C# 8. The method GetANullString is defined to return a type string?, so null is allowed, and this method just returns null. The method GetAString is defined to return a type of string, so null is not allowed. With the method PassAString, the parameter is defined to receive string. Here, null is not allowed, and there’s no need to verify this.


public class NewAndGlory
{
public string? GetANullString() => null;
public string GetAString() => "a string";
public string PassAString(string s) => s.ToUpper();
}

view raw

NewAndGlory.cs

hosted with ❤ by GitHub

On the other hand, there’s the library TheOldLib, which makes use of the C# 7.0 compiler. This is defined in the project file TheOldLib.csproj with the element 7. The Legacy class defines the method GetANullString that just returns null, and the method PassAString that receives a string and does the usual testing for null before the string is used. This library also defines the interface ILegacyInterface, which defines a method that returns a string. This string could be meant to be nullable or not, with C# 7 this cannot be specified in the interface.


public class Legacy
{
public string GetANullString() => null;
public string PassAString(string s)
{
if (s == null) throw new ArgumentNullException(nameof(s));
return s.ToUpper();
}
}
public interface ILegacyInterface
{
string Foo();
}

view raw

Legacy.cs

hosted with ❤ by GitHub

C# 8 Application using Libraries built with C# 7 and C# 8

Now let’s get into the C# 8 Console application that references both the old and the new libraries. Using the class NewAndGlory, as excpected the result from the method GetNullString can only be written to a string? type. Trying to pass null to the method PassAString retsults in the the compilation error cannot convert null literal to non-nullable reference or unconstrained type parameter.

Invoking the Legacy class where the method GetANullString returns null, the result can be written in a string type. Because this library is not implemented with C# 8, the C# 8 compiler doesn’t create a warning. This only happens with new libraries. It’s also possible to invoke the method PassAString and passing null. Using legacy libraries too many errors would be shown, that’s why libraries not built with the new compiler are dealt with differently.


var newglory = new NewAndGlory();
string? s1 = newglory.GetANullString();
string s2 = newglory.GetAString();
// string s3 = newglory.PassAString(null); // error: cannot convert null literal to non-nullable reference or unconstrained type parameter
var old = new Legacy();
string s4 = old.GetANullString(); // no error, s1 is null!
string s5 = old.PassAString(null); // no error

view raw

Program.cs

hosted with ❤ by GitHub

The method Foo of the interface ILegacyInterface defined in the library built with the C# 7 compiler returns a string. How can this be implemented with C# 8? As you can see in the following code snippet, the interface can be implemented in both ways – returning a nullable string, or a non-nullable string. This is a good way, as with C# 7 it was not possible to declare how this was meant.


class SomeClass : ILegacyInterface
{
public string? Foo() => null;
}
class AnotherClass : ILegacyInterface
{
public string Foo() => "a string";
}

view raw

Program.cs

hosted with ❤ by GitHub

Interfaces declared with the C# 8 compiler do need the correct implementation in regard to nullability.

C# 7 Application using Libraries built with C# 8

From an old application (an application using C# 7 or earlier), the new C# 8 built library can be used like any other .NET library. The new app doesn’t see nullable types such as string?, and sees string instead – which is nullable for C# 7 anyway. The non-nullable string type becomes a nullable string type. This way, the old application can make use of the new library. Of course, now the new library has the same issues in regard to nullability as the old library:


var glory = new NewAndGlory();
string s1 = glory.GetANullString();
string s2 = glory.GetAString();
string s3 = glory.PassAString(null); // having a NullReferenceException here!

view raw

Program.cs

hosted with ❤ by GitHub

Invoking the PassAStringMethod and passing null generates a NullReferenceException, when the ToUpper method is invoked on the string. This is the exception we want to avoid, but it still occurs in such an interop scenario. To avoid this, in the C# 8 library we can still check for null and throw a ArgumentNullException which is not required with C# 8 clients. Maybe the next version of the C# 8 compiler automatically creates this implementation with non-nullable types for older C# clients – but it’s only needed for the time when older than C# 8 compilers are used.

Summary

Non-nullable reference types is a new C# functionality that will get rid of many of the NullReferenceException exceptions. This is made possible by changing the default behavior of reference types. Although the default behavior changes, a new C# 8 application can still use old libraries, and a old C# application can make use of new C# 8 libraries. Nullability is implemented by using attributes, which makes this possible.

What do you think about this new C# 8 feature?

Get the complete sample from More Samples!

Before C# 8 is released, read about all the cool C# 7 features in the book Professional C# 7 and .NET Core 2.0!

Enjoy programming and learning,
Christian

If you found this information valuable and want to return me a favor, then buy me a coffee.

Buy Me A Coffee

46 thoughts on “C# 8 & No More NullReferenceExceptions – What about legacy code?

  1. I really like the lean and backwards compatible implementation.It’s nice you can now be explicit about if a reference type variable can be NULL and this might save a lot of null check bloat.

    But while this will help to spot unintentional NULL values in your code at design time, it might even make the issue with exceptions far away from the actual problematic code worse. I can imagine String.Empty will be the new NULL now and instead of getting an exception at least far away from the code that causes the issue, you might now not even get an exception at all, just a wrong result at the end.

    So while I think that overall this will be a nice improvement, but the devil might hide in the details.

    Liked by 1 person

    1. I don’t think string.Empty will be the new NULL now – you can use all the string methods on an empty string. Of course, this might lead to a result not expected, but this was always the case.

      The non-nullable reference types are not only about strings – I expect it a huge improvement overall.

      Of course, the devil might hide in the details – but I didn’t see such issues in the applications where I tried the new compiler yet.
      I’m just expecting that we want more as it can’t catch all NullableReferenceException issues.

      Like

      1. I totally agree Christian, it will be a huge improvement. A lot of issues common today will go away with it, so even if my String.Empty prediction comes true, *overall* it’s definitely something to look forward in C# 8.

        Just to clarify my String.Empty example: I’m not worried about existing code, this should work fine. I’m worried about how new code will be written with C# 8. Today, when you declare a variable, you’ll set it to NULL if you don’t have any meaningful default. This can result in exactly the problematic situation you mentioned if you have a code path where you erroneously never set a value on this variable and somewhere far down in your code, this will cause a NullReferenceException.

        With C# 8 you can’t set your string (or your complex type) to NULL. So I imagine many will initialize their strings to String.Empty (or their complex types to a default instance). This will prevent NullReferenceExceptions. But if you have the same error in your code as before and some code path never assigns an actual value, you still have the same bug, but you might never run into an Exception.

        So in those cases, I think we lose NullReferenceExceptions as a less than ideal, hard-to-debug but still clearly recognizable “last line of defense” against erroneously uninitialized variables. But maybe I’m wrong and this feature will help us all write better code to start with 🙂

        But I agree with your last sentence, we’ll never be able to catch any mistake a developer makes upfront, and yes I think this possible tradeoff is worth it in the big picture.

        Liked by 1 person

      2. Ah and just by the way I think one possible solution to this “issue” is in your blog post already:

        Within a single method, declare your local reference type variables as nullable as if it is still 2017 :). But when passing them on (by returning them from your function, requiring them in another method …) don’t make them nullable. This allows the compiler to enforce setting a value on every code path.

        But it has to be done that way, and consistently, and it won’t protect you against NullRefs inside a single method. But I think that’s ok. At least to me it seems to be a good possibility … or do you think even within local methods we should avoid having nullable variables?

        Bernhard

        Liked by 1 person

  2. Bernhard, true, I think that’s a good scenario to declare a variable locally as nullable and return it as non-nullable. The compiler checks all the paths to verify if it is always assigned.

    With some methods it’s also ok to return nullable reference types. Calling this method the compiler complains if you don’t check the result before use.

    But there are still some scenarios not verified, e.g. if you create an array of objects. Using these, NullReferenceException is still possible.

    Like

    1. @tsaukpaetra – sorry it’s distracting to you. I try to write more articles instead of going through more loops checking.
      After reading through this “distracting” text, what are your thoughts on the C# 8 features?

      Like

      1. I think using an automated spell checker would mean you have to do less manual checking. I prefer to eliminate errors by adding a system that prevents me making mistakes rather than relying on finding them manually every time.
        This is also why I like the the new nullability features of C# 8.

        Like

      2. Apparently I can’t delete comments. Sorry that previous was was a bit short. I would expect ‘mehtod’ to have been picked up by an automated process but I agree with your sentiment that it’s not that big a deal.

        Liked by 1 person

  3. This change in C# rightly makes you have to think about what assignation you want your reference types to have, making the programmer more concious of potential NullReferenceExceptions.

    Liked by 1 person

  4. An interesting , but flawed, article, thanks. I think I found it hard to follow because of the „baggage“ I have about Type ‚String in .NET being such an atypical beast. I never have null reference errors involving strings. Not a single error at build or run of this code:
    string foo0;
    string foo1 = null;
    string foo2 = String.Empty;
    bool wtf0 = foo0 == foo1; // true
    bool wtf1 = foo0 == foo2; // false
    bool wtf2 = foo1 == foo2; // false
    For me, examples using classes would have been more edifying.

    Liked by 1 person

    1. Many thanks for your comments. I didn’t thought it would be hard to replace string for any other reference type.
      With your example, you do not result in a NullReferenceException with other reference types as well:
      Book book0 = GetANullBook(); // returns null
      Book book1 = null;
      Book book2 = new Book();
      bool wtf0 = book0 == book1: // true
      bool wtf1 = book0 == book2; // false
      bool wtf2 = book1 == book2; // false
      However, with your string example you have the compilation error “Use of unassigned local variable ‘foo0’, that’s why I’ve done a small change with the Book type – it’s the same behavior.

      Like

      1. “I didn’t thought it would be hard to replace string for any other reference type.”

        My problem is that ‘String is a kind of chimera in .NET : it’s an immutable reference Type;
        it;s a “container” with no default “size:’ where it is in memory is complex compared to other reference types. imho, it’s not a good modal exemplar for the issues you seek to illustrate.

        ” with your string example you have the compilation error “Use of unassigned local variable ‘foo0’, ”

        That error will be thrown only if you have your project’s build option “Treat warnings as errors” set to ‘All. It is not “inherently” fatal 🙂 in the way many other errors are.

        cheers, Bill

        Like

      2. “Book book0 = GetANullBook(); // returns null”

        Note that in my code example the corresponding variable was simply declared, and your code has an error: you user a colon rather than a semicolon here: bool wtf0 = book0 == book1: // true

        Are you aware of the unusual behavior in .NET where a variable, either string or class can be declared without initialization at class level scope, and then used with no compile error, whereas the same type of declaration inside a method will throw a compile time error even if you have your project’s build option “Treat warnings as errors” set to ‘None ?

        Like

      3. This is defined behavior, and was a mentioned as a feature with C# 1.0. Declaring a member variable, it’s initialized in the default constructor that’s automatically generated. In case you declare a custom default constructor, you’ll get a compiler error as well.
        If you declare a local variable within a method, no constructor is generated. That’s the reason for the compiler error.
        Sorry for missing the : in the code section of my comment.

        Like

  5. Bitmap bmp = FindBitmap(“fred.bmp”);
    if (bmp == null)
    {
    // looks elsewhere
    }

    This code isn’t going to compile with C#8. There will be millions (literally) of this type of example. No one in their right mind would make a change to a compiler that would break that code.

    Liked by 1 person

    1. Robert,
      if the FindBitmap method is in an older library, and this code is in a new C# 8 application, nothing changes. No error from the compiler, everything is good. The C# 8 compiler does not complain with legacy libraries.

      If both the FindBitmap method as well as the calling code is new C# 8 code, it needs to be decided if FindBitmap should be allowed to return null. If this is the case, the FindBitmap method should return Bitmap?. If the FindBitmap method returns Bitmap?, you get a warning assigning it to a non-nullable type.
      All what’s needed in this case is add the ? to the return type of FindBitmap, and a ? to the declaration Bitmap? bmp.

      If FindBitmap is declared to return non-nullable Bitmap, it cannot return null. The check with null is unnecessary.

      You can switch to C# 8 at your pace, as long as all the libraries are not updated nothing happens.

      Often I see such examples without checking for null. In that case we now get a NullReferenceException sometimes – e.g. when FindBitmap finds nothing. This will be solved with C# 8. To get rid of these issues, I think it’s ok to add a few ? to the existing code when null should be allowed.

      Like

      1. That code is likely to be in my application, and lots like it. I do not want to have to put lots of irritating silly question marks (LISQ) everywhere just to get my code to compile. It will end up looking like Swift. Have modes of compilation where I can see these issues – that is a good idea. However, I know that is good code, I don’t need a compiler breaking it for me.

        Like

      2. It’s great that you don’t experience any issues with NoReferenceExceptions. For me it’s also great to immediately see in the editor which methods can return null, and when I don’t need to write null verification code. The C# 8 compiler just creates warnings, and you can define to ignore these warnings with your projects.

        Like

      3. “Often I see such examples without checking for null”.

        Ok, and those situations happens because there are bad programmers around. And for those bad programmers, now decent programmers will have to use an additional “?”.

        There are more situations where a programmer want to tell explicitly that a method could return nothing (where this “nothing” has a logical sense), or there are more situations where a method should always return something filled in (I mean, not empty values), but the programmer forgot to properly manage those exception ? I would tell the first.

        I don’t think that string.Empty (and so on) will be the new null. I think that worse scenarios will happen . Applications with wrong results, harder to debug (if and when someone will complain that something is going wrong…). If programmers was lazy till now, they will be the same after this C#8 feature, and they will not use the “?” when they should. We’ll see tons of database tables with nullable fields, filled with empty strings, and tons of properties that should point to null, filled with empty strings. And tons of legacy code, that point to those same tables, that will give wrong results or other runtime errors.

        Like

      4. @Piero – I must confess that I myself missed some cases where a method returned null that I didn’t expect it would. I also used many libraries from Microsoft where NullReferenceException exceptions happened within these libraries at runtime. Probably they have many bad programmers as well.
        I prefer to get more help from the compiler, and to write less code myself.

        From source code analysis I’ve heard from the C# team it’s the second case: more situations when a method should always return something.

        I don’t get it why there could be worse scenarios. If the ? is not used when it should be, a compiler warning will occur. Of course it’s bad if the warnings are ignored. I always go for 0 compiler warnings. Probably I’ll have the configuration to treat these warnings as errors in the teams I’m working with.

        Like

    1. yes, it’s a warning. The previous version resulted in a compile time error. The May version of the C# 8 preview results in a warning. There might also be a compiler switch in a later release.

      If you use
      var bmp = FindBitmap()
      with your code, there’s not an issue with nullability – it’s the type that’s returned.

      For cases such as
      Bitmap bmp = FindBitmap();
      if (bmp == null)
      {

      There might also be tool support like “fix all in the solution” to add ?.

      See C# team meeting notes at https://github.com/dotnet/csharplang/blob/c2327f101b52441a5f6db3257972b52a2bd75997/meetings/2017/LDM-2017-08-07.md

      Like

      1. I am not really a fan of ‘var’.

        Some people have large code bases and really don’t want to have to investigate a 1000 instances of this. The best they can achieve is that they modify them all successfully.

        Like

  6. The fundamental thought process behind this change I believe to be incorrect. We are now going to say a reference type has to point to something but by definition a reference not pointing to anything (i.e. null) is a valid state. The motivations behind the change are valid however. Using the ! by a type would be useful and a better approach IMO.
    Foo! x —— x can NOT be null
    As apposed to
    Foo x —— x can NOT be null
    The first is more readable and achieves the goal without breaking changes. Including defining a parameter as one that can NOT be null.

    Like

    1. Let’s assume to have the method declaration
      void Foo(Book! b)
      for not nullable books, and
      void Foo(Book b)
      for nullable books. With the new compiler checks, we would get compilation warnings if the b is not verified to be not null in the second case. This also results in breaking changes.

      The C# team works on nullability since many years, and your variant was worked with as well.
      It looks like there’s more code where null should not be used compared to the code where null is useful.
      Existing code needs to be changed to get the full features from this behavior. IMO, the C# team made the correct choice. Let’s fix bugs from existing code with the help of the compiler and the help of the tools.

      Like

  7. Instead of learning (… removed by moderator) to code better, C# team decide “let’s break compiler!”. Great solution! MS makes its grave deeper and deeper.

    Like

    1. I think this is all about writing better code. It allows you to be concise and show clear intent in your code. Currently there is no way for a class to tell the outside world if a reference type parameter/property/… is allowed to be NULL. But now there is.

      Nothing “breaks” in the compiler or in existing code, as was discussed in Christian’s article and in the comments extensively.

      And being able to spot issues at compile time is ALWAYS a good thing. No developer is perfect and one single mistake can bring down a whole application. Or, you are this perfect little dev that never ever suffered from a NullRef Exception … in that case, C# might not be advanced enough for you anyway and I’d recommend that you exclusively develop in LISP from now on and ignore the chitchat of lesser ones like us.

      Liked by 1 person

      1. Just to be clear: when talking about method parameters, there is a way to tell the outside world about params that can be NULL: optional parameters. But it’s not telling the same story IMHO and it applies only to params anyhow, not to properties, public fields or return values.

        Liked by 1 person

  8. It seems wrong that intellisense on legacy libraries does not show all string parameters as string? since they are technically nullable even if they aren’t meant to be.

    Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.