Continuing our the upgrade process from last time, in this post we’ll explore the changes required to our
The Null Object Pattern
RoutedCommandSink, we get a warning in our constructors when we try to assign null to
_canExecute. We could make this a nullable member, but there’s a better approach: by always assigning a value, we can simplify other code.
_canExecute with a simple lambda expression that always returns true:
We can now modify
CanExecuteCore() by removing the guard clause:
Allowing Null Fields
By contrast, we’ll just change
_commandHost into a nullable value:
Caller Member Name
In our class
ViewModelBase, we use the
[CallerMemberName] attribute to declare our event trigger methods. This is useful as it helps to guarantee that the name passed to the method (and used within) correctly identifies the caller. Elimination of magic strings is almost always a good thing.
With nullable reference types, we run into a problem.
[CallerMemberName] to work, the property must be declared with a default null value - but when we consume the value, we know that it will always be present.
One approach is to add a useless guard clause, throwing an exception if the value is missing (which will never happen).
Another, better, approach is to apply the “dammit” operator (
!) to assert that the value won’t be null. This useful operator allows us to instruct the compiler that we know better than it does, and that this value is safe. (Observe that the operator does not instruct the compiler that the value is non-null … if it did, the construct
null! would be a compiler error.)
Propagation of Nullability
Subscriptions to our
ReduxStore need to support null values, so any of the handlers we use when calling
Subscribe() needs to have nullability set appropriately.
VocabularyBrowserViewModel, our constructor has a warning on this statement:
While the first parameter returns a
VocabularyBrowserScreen? (nullable), the method
RefreshFromScreen only accepts
To fix this, we need to modify the declaration of
RefreshFromScreen to accept a nullable value:
I’ve found this a few times - that the nullability modification required to fix a bug can be somewhere quite different to the location where the compiler emits a warning.
A bug in the factory
Create() method of
ViewFactory, we’re getting a warning about a possible
Can you see the bug?
The intent is to throw an exception if we failed to find a view type to match the provided view model - but we’re instead checking the view model type. When the bug is fixed, the warning goes away.
I’m enjoying the way that nullable reference type checking by the compiler is revealing subtle bugs in my code.
Consistency of Nullability
When subscribing to our redux store, the constructor for
AddVocabularyWordViewModel showed this warning:
CS8631: The type
WordTutor.Core.AddVocabularyWordScreen?cannot be used as type parameter ‘V’ in the generic type or method
.Subscribe<V>(Func<WordTutorApplication, V>, Action<V>). Nullability of type argument
WordTutor.Core.AddVocabularyWordScreen?doesn’t match constraint type
While I understood what the error was saying, it wasn’t at all clear what change was needed to make the warning go away. Worse, these C# features are so new that there’s almost nothing published online to read.
After (literally!) several hours of reading, thinking, and experimenting, I posted online to see if anyone else had run into the issue. In short order, someone came back with a solution.
Subscribe() method has this declaration:
One cause of the CS8631 error is the generic type constraint on
V. It makes sense to require that the nullability of
V and its type constraints must agree. If, for example,
V was nullable and the constraint was not, you would end up with the compiler failing to warn you about some potential null handling errors.
There’s another problem - the delegates
Func<T, V> reader and
Action<V> whenChanged are declared as non-nullable, preventing the use of this subscription with nullable values. We already have code the uses null values, so we need to change this.
We end up with this modified declaration:
At first, I didn’t understand why this required the class constraint to be added, but after reading “Try out Nullable Reference Types” (specifically the section “The issue with T?”), I do (somewhat!) see the problem they’re avoiding. But, I’m not sure I can explain it yet in my own words. Go read all the gory details; it’s stuff you’re going to want to know.
Since we can’t do method overloading based solely on different type constraints, we need to split the original method into two: one for reference types, and one for value types.
You’ll note above that I said “One cause of the CS8631 error” … there’s another way that it can occur. The warning can appear if nullability differs between a type constraint and the type itself. I originally had this declaration for
However, this conflicts with a type constraint
T : IEquatable<T?> because the two differ in the nullability of the type constraint. One uses
VocabularyWord (not nullable) and the other uses
T? (nullable). I had to modify the declaration to this:
After thinking this through, I came to the conclusion that this made sense - after all, it’s valid to ask if a word equals null, even if the answer is always going to be “no”.
Migration to use the nullability features of C# 8 will involve a bit of head-scratching, due in no small part to the need to reason through what null means for your code. It’s worth it though - the resulting code will be clearer, and you’ll almost certainly find and eliminate some subtle bugs.