Upgrading Visual Studio 2019
Using the Visual Studio Installer, I upgraded Visual Studio 2019 to the latest available version; this brought down the latest versions of .NET Core and the C# compiler automatically. If you want to upgrade to .NET Core 3.0 separately, there’s a download to do that.
Nullable Reference Types
One of the headline features in C# 8 is the introduction of nullable reference types. This language feature addresses the long-standing issues with
NullReferenceException, helping you to write code that avoids this most common of errors.
To turn it on for our projects, we need to set these two values in each of our
With those turned on, what do we find from a full compile? We’ve been somewhat careful with null references so far in the project, have we been careful enough?
It’s important to note at the outset that all of the feedback from the compiler is in the form of warnings, highlighting code that requires closer attention. The compiler isn’t omniscient - and the halting problem is unsolvable. The point of the feature is to highlight probable problem areas, nothing more.
AddVocabularyWordScreen, we see the warnings in the constructor, complaining that null isn’t valid to apply to a non-nullable type. Since the point of this constructor is to make a near-clone of an existing instance, we want to permit null values so that we know to copy properties from the original. So we change the parameters to string? to indicate they should be nullable.
Note that we don’t get any warnings that the existing
original is null test is redundant. The goal of nullable reference types is to help you find potential issues, not to reduce the safety of your existing code.
The pattern of needing to mark constructor parameters as nullable repeats with all of the private near-clone constructors we’ve previously written for our immutable types.
Equals() methods on
AddVocabularyWordScreen all have warnings for the same reason: it’s valid to pass null, so we mark the parameters as nullable:
Again, we have a pattern that repeats across many of the types we’ve already written.
VocabularyBrowserScreen, it’s quite valid for us to have no selection. You might recall that we recently talked about how no selection is something we need to handle.
Marking the property as nullable is an easy fix:
Selection can be null, it’s not surprising to get a warning in the
Equals(VocabularyBrowserScreen?) about a potential issue:
To fix this, let’s declare a static member that can do the comparison for us:
We can then use that to do the comparison:
Further down, in
GetHashCode(), we take a different approach by using the Elvis operator (
?.) to protect the call and provide a useful default:
Fields not initialized at construction
The constructor for
ReduxSubscription<TState, TValue> is giving us an interesting warning:
Non-nullable field ‘_lastValue’ is uninitialized. Consider declaring the field as nullable.
Making the obvious change, we modify the field declaration to this:
But then we get this error instead:
A nullable type parameter must be known to be a value type or non-nullable reference type. Consider adding a ‘class’, ‘struct’, or type constraint.
We don’t want to constrain redux subscriptions to just particular types, so adding a type constraint isn’t a good solution for our context.
ReduxSubscription<TState, TValue>, we compare
_lastValue with the value we’ve just read, as a part of deciding whether to publish the value.
If the value we read is a null, then we won’t ever publish the value until it changes to something else.
Surely this is a bug? We should always publish the first time, and then only publish when the value changes. Adding a new unit test, we verify that this is indeed a bug!
After fixing things up, we address the original warning by modifying the declaration:
The field is marked with the
[AllowNull] attribute - this is us telling the compiler “trust us, we know what we’re doing”. We also need to ensure the field is always initialized.
That’s not a bad process for an upgrade - a handful of places where we needed to explicitly indicate that null was accepted, one place where we needed to tell the compiler we knew what was going on, and three actual bugs identified and fixed.