Skip to content
Ian Griffiths By Ian Griffiths Technical Fellow I
C# 8.0 nullable references: defeating the point with empty strings

In this post, I'll explain why you should avoid the temptation to apply a quick, and seemingly easy fix for a particular warning that can occur when you start using C#'s nullable references feature. You can silence the compiler by initializing string properties to an empty string, but you really shouldn't.

If you enable nullable references on an existing project, you will often see large numbers of warnings. A particularly common one is CS8618, complaining that a non-nullable property is uninitialized, e.g.:

A C# property of type string, called FavouriteColour, with auto get and set accessors, and a Visual Studio mouse-over message stating that this non-nullable property is uninitialized, asking us to consider declaring the property as nullable.

With a string property, you might be tempted to initialize it with an empty string, e.g.:

public class Person
{
    // This gets rid of the CS8618 warning, but it is
    // A BAD IDEA!
    public string FavouriteColour { get; set; } = "";
}
The Introduction to Rx.NET 2nd Edition (2024) Book, by Ian Griffiths & Lee Campbell, is now available to download for FREE.

This will make the warning go away, but it defeats the point of enabling nullable reference types. The whole reason C# 8 introduced its Nullable Reference Types feature was to avoid situations in which properties and other variables do not have a usable value. Before nullable references were available, we had do to one of the following:

  1. Use ad hoc means to attempt to ensure that variables have useful values by the time we use them, with no help from the compiler.
  2. Check for null before attempting to use variables and then decide what on earth to do if we get a null.

With nullable reference types enabled, then we can indicate explicitly the cases in which it is useful to our application to be able to represent the absence of a value, but by default the compiler will give us a great deal of assistance in ensuring that we don't accidentally try to use something that isn't there.

Initializing a property (or any other variable) with an empty string typically undoes any benefit of enabling nullable reference types, because the empty string effectively becomes null by another name. The fundamental problem remains: we have variables which might sometimes contain a special value that signifies the absence of a real value, we're just using a different special value now. And unlike null, the empty string gets no special analysis from the compiler.

The basic problem that CS8618 is warning us about above remains: it's possible to finish constructing an instance of our Person class without having provided a meaningful value for FavouriteColour. (If having no favourite colour is a position you need your application to support, you can just use string?, because nullability is a reasonable way to indicate that. A better way than using an empty string, in fact.)

Programming C# 10 Book, by Ian Griffiths, published by O'Reilly Media, is now available to buy.

If your application absolutely needs a property to be present, the best way to do this is to use the correct-by-construction technique describe in my recent blog on nullable references and serialization in which you define constructors that force code to supply all required values:

public class JsonSerializableType
{
    public JsonSerializableType(
        int favouriteInteger, string favouriteColour)
    {
        FavouriteInteger = favouriteInteger;
        FavouriteColour = favouriteColour;
    }

    public int FavouriteInteger { get; set; }

    public string FavouriteColour { get; set; }
}

If constraints prevent you from using this technique (e.g, some frameworks require types to supply a zero-argument constructor) then that blog describes some alternatives that still enable you to retain the fundamental benefit of nullable reference types.

It's not really about null

The critical insight here is that none of this is really about null. It's about knowing whether a valid value is available at a particular point in your code. C# has always gone to some lengths to try to ensure this with its definite assignment rules, but it was easy to undermine the compiler's attempts to keep you safe: assigning null to a reference type variable would satisfy the definite assignment rules. These rules were not designed to determine whether the value assigned was actually usable.

The best hour you can spend to refine your own data strategy and leverage the latest capabilities on Azure to accelerate your road map.

In fact the same problem exists for value types. The compiler will require a local variable of type int to be initialized before you attempt to read it, but it won't stop you from picking some arbitrary value just to keep the compiler quiet. If you set an int to 0, the compiler has no way of knowing whether that 0 correctly reflects something about the application's state, or is just a convenient default to use until the real value is available.

Using 0 as a placeholder value in an int variable before you have the real value is conceptually no different from initializing a reference variable to null. Practically it has one difference: at least a null reference will fail conspicuously if you erroneously attempt to use it before it is ready; with an int, if you accidentally end up using something that was meant to be a placeholder value, you probably won't get a runtime error. You'll just get incorrect behaviour.

And that's what's so insidious about initializing a string variable with an empty string as a lazy way to handle the fact that you don't have the right value yet. You've still got the same basic problem you had before, but it will now fail in less conspicuous ways, increasing the chances of incorrect program behaviour going undetected. Better to crash quickly before your program's logic error can cause lasting damage.

If you want to get the benefits that nullable reference types offer, you can't just do the minimum required to keep the compiler happy. (If you want to take that approach, the quickest path is just to turn the feature off again.) To reap the potential value, you need to use techniques such as those shown in my recent nullable references and serialization blog. Let the compiler guide you in reworking your code to reduce the chances of getting unintended nulls. It is by working with, not against the nullable references feature, that you will see the greatest improvements.

Ian Griffiths

Technical Fellow I

Ian Griffiths

Ian has worked in various aspects of computing, including computer networking, embedded real-time systems, broadcast television systems, medical imaging, and all forms of cloud computing. Ian is a Technical Fellow at endjin, and Microsoft MVP in Developer Technologies. He is the author of O'Reilly's Programming C# 10.0, and has written Pluralsight courses on WPF (and here) and the TPL. He's a maintainer of Reactive Extensions for .NET, Reaqtor, and endjin's 50+ open source projects. Technology brings him joy.