Code with intent!
Tags: codequality, dotnet, beginners, programming
Published: 2023-02-10 08:11:10

Now that Umbraco has moved to .NET 6 and up, I've been reviewing more pull requests from my coworkers on this new framework and I noticed some struggles with the new [null state analysis](https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references#null-state-analysis) feature. In this article, I want to briefly explore what this feature is, why one might struggle to deal with it and why it is so important for code quality.

## Nullability
In .NET Framework, the value `null` is an exclusive feature for reference types. In order to enable the use of `null` for value types, we use a wrapper `Nullable<T>`. With some syntactic sugar, that turns into `T?`. It's purely for practical purposes to make types like integers support `null`.

Moving to .NET 6, we get introduced to a new concept: Nullable reference types. In short: you're now allowed to also add `?` behind reference types to indicate that this value might contain `null`. .NET 6 also introduces a new static analyzer that attempts to identify code that badly handles potential `null` values.

## Struggling to adapt
It's no surprise that experienced developers on .NET Framework feel confused. Why would I use this at all? I already know that reference types can contain `null`, what's the difference? Looking at my company, I often see one of two things:

- "This code used to just work, now the _compiler says_ I'm doing things wrong. I'm ignoring / disabling it"
- "Oh, the _compiler says_ this might be null, I'll add a null check"

And why wouldn't you be like one of those? Especially when converting an old code base to .NET 6, the amount of warnings that you suddenly get, is rather overwhelming. When porting [the URL Tracker package](https://github.com/Infocaster/UrlTracker) to Umbraco 9 and 10, I had over 2000 warnings in my code suddenly. One does not simply adapt to nullable reference types and it may seem more productive to turn the feature off.

## It's important though
The importance of this feature is in conveying your intent. Though variables of a reference type can contain `null`, in some code you expect them to have a non-null value. If you effectively manage your expectations regarding `null` values, your code becomes significantly more expressive.

When looking at the quotes, what stands out is that this behaviour is led by the compiler as if it's the boss. The compiler is not your boss, it is your friend. It's telling you that your expectations are ambiguous. You should aim to tell the compiler one of these:

- "_I expect_ that this value will never contain `null`"
  ```csharp
  //  Notice the ! in this line
  string myVar = MyMethod()!.ToLower();
  ```
- "_I expect_ that this value might contain `null`"
  ```csharp
  // Notice the ? in this line
  string? myVar = MyMethod()?.ToLower();
  ```
- "_I'll handle_ the null value"
  ```csharp
  string? myPotentialNullVar = MyMethod();
  if (myPotentialNullVar is null) myPotentialNullVar = "My default string";

  // Notice the lack of ? here because we already checked for null in the if statement
  string myVar = myPotentialNullVar.ToLower();
  ```
All these examples show clearly whether or not it was intended for `myVar` to contain `null`. Managing your expectations automatically improves your code quality. It prevents questions like: "Is this expected behaviour or is this a bug?". It gives you and your team more confidence.

## Some practical examples
Let's have a look at some examples that I've come across and see how nullable reference types make your code more expressive.

### Method signatures
In .NET Framework, one might write a method like this:

```csharp
public Thing GetFirstThing()
{
    return _myListOfThings.FirstOrDefault();
}
```

This however, is ambiguous. Your method promises to return an instance of `Thing`, but might actually return `null`. As a consumer of this method, I might get the wrong expectations. In .NET 6, you should change this code to one of these options:

If you expect there to always be an element in the list:

```csharp
public Thing GetFirstThing()
{
    // There should always be an item in this list
    //   If there isn't, then something is broken, so an exception should be thrown
    return _myListOfThings.First();
}
```

If you expect that the list might be empty:

```csharp
// Notice the ? in the return type
public Thing? GetFirstThing()
{
    return _myListOfThings.FirstOrDefault();
}
```
Now your intentions are well-defined.

It's not always that simple though, so let's have a look at another example:

```csharp
public static string DefaultIfNullOrWhiteSpace(this string input, string @default)
{
    return !string.IsNullOrWhiteSpace(input) ? input : @default;
}
```
I like this method a lot, it reduces the amount of code that I write and improves expressiveness. It's ambiguous to the compiler in .NET 6 though, so I'd change the code to this:

```csharp
public static string? DefaultIfNullOrWhiteSpace(this string? input, string? @default)
{
    return !string.IsNullOrWhiteSpace(input) ? input : @default;
}
```

However, if I try to use the method, I'm getting some unnecessary warnings:

```csharp
// WARNING: Converting possible null value to non-nullable type
string myVar = myPotentialNullVar.DefaultIfNullOrWhiteSpace("My default")
```

I know for sure that the output of the method will never be `null`, but the compiler is not clever enough to understand that as well, so we have to help the compiler a little bit:

```csharp
[return: NotNullIfNotNull(nameof(@default))]
public static string? DefaultIfNullOrWhiteSpace(this string? input, string? @default)
{
    return !string.IsNullOrWhiteSpace(input) ? input : @default;
}
```

The `NotNullIfNotNull` attribute tells the compiler that the return value can never be null if the parameter `@default` is not null. This is just one of many, you can read more about these attributes in [the microsoft documentation about null state analysis](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/attributes/nullable-analysis).

### Class definitions
In .NET Framework, I might define a POCO like this:
```csharp
public class MyModel
{
    public string MyProperty { get; set; }
}
```

That's not good enough for the .NET 6 compiler though: `MyProperty` is never initialized with a value, so it potentially contains `null`. You can fix this in one of several ways:

```csharp
public class MyModel
{
    public MyModel(string myFirstProperty)
    {
        // Method #1: assign the value in the constructor
        MyFirstProperty = myFirstProperty
    }

    public string MyFirstProperty { get; set; }

    // Method #2: assign a default value to the property
    public string MySecondProperty { get; set; } = "My default value";

    // Method #3: make the property nullable
    public string? MyThirdProperty { get; set; }
}
```

Here it's important as well to convey your intent. Don't just make the property nullable because the compiler is complaining. Think about what you expect this value to be and act accordingly.

Now I'm personally still struggling with a few use cases here. Request models in ASP.NET for example are defined as POCOs. I might expect a property on the POCO to always have a value, because it's required. I make sure of that with validation attributes:

```csharp
public class MyRequestModel
{
    [Required]
    public string MyProperty { get; set; }
    // WARNING Non-nullable field must contain a non-null value when exiting constructor
}
```

I haven't found a way yet to tell the compiler that this property will never contain null, since it is required and I use model validation. In this case, I also can't assign the property in the constructor. Therefore, I resorted to this workaround:

```csharp
public class MyRequestModel
{
    [Required]
    public string MyProperty { get; set; } = null!;
}
```

Albeit not pretty, it does get rid of the warning, while sufficiently showing my intent.

## Final thoughts
I love this feature. It's important to me that code is expressive and conveys the intentions of the developer behind it and this feature improves expressiveness a lot. I understand that it may be confusing for experienced .NET Framework developers though. The feature also adds some different challenges, some of which I haven't quite worked out myself.

So should you use this? It depends. If you're lucky enough to start a brand new solution, I'd say you should. If you're converting an old code base to the new framework, then you should probably agree on it with your team. The amount of warnings you might get at the initial conversion may lead to notification fatigue, but if you solve them one at a time, you'll benefit from it in the long term.

So what are your thoughts on nullable reference types and null state analysis? Let me know in a comment!

Thank you for reading! 😊