Modernizing a codebase for C# 9

There are lots of cases that you can improve. The examples use nullable reference types, but only the WhenNotNull example requires it.


Use the property pattern to replace IsNullorEmpty

Consider adopting the new property pattern, wherever you use IsNullOrEmpty.

string? 
hello = "hello world"; 
hello = null;  
// Old approach 
if (!string.IsNullOrEmpty(hello)) 
{     
Console.WriteLine($"{hello} has {hello.Length} letters."); 
}  

// New approach, with a property pattern 
if (hello is { Length: >0 }) 
{     
Console.WriteLine($"{hello} has {hello.Length} letters."); 
}

You can use a similar super-powered set of checks on arrays. Note that the "Old approach" isn't compatible with nullability, but the "New approach" is. It is due to the compiler only tracking variables not array indices.

// For arrays 
string?[]? 
greetings = new string[2]; 
greetings[0] = "Hello world"; 
greetings = null;  
// Old approach 
if (greetings != null && !string.IsNullOrEmpty(greetings[0])) 
{     
Console.WriteLine($"{greetings[0]} has {greetings[0].Length} letters."); 
}  

// New approach 
if (greetings?[0] is {Length: > 0} hi) 
{     
Console.WriteLine($"{hi} has {hi.Length} letters."); 
}

Here is some related code experiments on nullability and arrays: https://gist.github.com/richlander/ca6567039906da4e1fcfba557b6ccb63


Simplify checks to multiple constant values

You can now test a value against multiple constant values.

ConsoleKeyInfo userInput = Console.ReadKey();  

// Old approach 
if (userInput.KeyChar == 'Y' || userInput.KeyChar == 'y') 
{     
Console.WriteLine("Do something."); 
}  

// New approach with a logical pattern 
if (userInput.KeyChar is 'Y' or 'y') 
{     
Console.WriteLine("Do something."); 
}


Use NotNullWhen for bool return methods with nullable out parameters

You can make it easy to call methods that return bool and whose signature include an out param with a nullable annotation, using the NotNullWhen attribute. In the typical pattern, the attribute tells the compiler that the out parameter is set when the return value is true. In that case, you don't have to check the out parameter for a null reference, if you guard the use of the reference within an if statement, conditional on the return value.


You can search a codebase for this pattern with the following regex, to find opportunities to use NotNullWhen.

bool.*(out).*(\?)

I used VS Code for this, as demonstrated below.


It will match methods like the following:

public bool ListenToCardIso14443TypeB(TransmitterRadioFrequencyConfiguration transmitter, ReceiverRadioFrequencyConfiguration receiver, out Data106kbpsTypeB? card, int timeoutPollingMilliseconds)

You can update the method with a NotNullWhen attribute, like the following:

public bool ListenToCardIso14443TypeB(TransmitterRadioFrequencyConfiguration transmitter, ReceiverRadioFrequencyConfiguration receiver, [NotNullWhen(true)] out Data106kbpsTypeB? card, int timeoutPollingMilliseconds)

The NotNullWhen attribute enables consuming code to skip checking for null for out param, even though it is annotated as nullable. Example: https://github.com/dotnet/iot/blob/idbnrt/src/devices/Pn5180/Pn5180.cs#L1138-L1142


You can then write this consuming code: https://github.com/dotnet/iot/blob/idbnrt/src/devices/Pn5180/samples/Program.cs#L168


You can only use this attribute if you target .NET Core 3.0+. The #if in Pn5180.cs example is only necessary if you also target .NET Core 2.1 or earlier. The same pattern applies to .NET Standard 2.1 and pre-2.1.


Use MemberNotNull for member fields and properties that are set in helper methods called from a constructor

A common pattern is setting a member fields and properties from helper methods that are called from an object constructor. This is useful if there is a lot of work to do (and you prefer clean constructors), or if you want to share logic across multiple constructors. Both approaches are sensible, but do not play nicely with nullability. The compiler cannot see that the member field or property is reliably set. The solution to this (.NET 5.0+) is the apply to the MemberNotNull or MemberNotNullWhen attribute on the helper method that assigns a non-null value to one or multiple member fields or properties. As a result, they don't have to be (necessarily) set to nullable, which is a nice thing to avoid.


Docs:

  • MemberNotNull

  • MemberNotNullWhen

  • dotnet/runtime #31877

Example usage: https://github.com/dotnet/iot/blob/idbnrt/src/devices/Bmxx80/Bmxx80Base.cs#L312

That example code targets both .NET Core 2.1 and .NET 5.0. That's why it using conditional compilation (#if) which isn't otherwise needed. This is what the code does in absense of that attribute being available, for .NET Core 2.1 and 3.1: https://github.com/dotnet/iot/blob/idbnrt/src/devices/Bmxx80/Bmxx80Base.cs#L90-L95.


Avoid ! (dammit operator), but do use for Dispose

I try to color within the lines as much as possible with nullable reference types. That means avoiding the use of the ! or "dammit" operator. I have found that it is better to to prefer nullable and the ? operator over '!'. Every time you using !, you are giving up on compiler checking. Why not just accept nulls, but with help from the compiler?


The one (very large) exception to this approach is dispose. For the sake of everything holy and virtuous, don't make a member nullable only to satisfy the requirements of dispose. You should feel free to assign null! to non-nbullable object members as part of dispose. All bets are off after dispose, so don't worry about the state of the object after that.


Source: paper.li

Recent Posts

See All

New C# Source Generator Samples

Phillip introduced C# Source Generators here. This post describes two new generators that we added to the samples project in the Roslyn SDK github repo. The first generator gives you strongly typed ac