Simplifying Null Handling in C# with Try Methods and Nullable Reference Types

Introduction

As developers, we’re always looking for ways to write cleaner, more efficient code. One common scenario we encounter is the need to handle potentially null values safely. In C#, nullable reference types have been a significant step forward in my opinion, but they can sometimes lead to extra checks and assertions.

I recently stumbled upon a neat trick that simplifies this process and wanted to share it with my team - and now with you.

Problem

Let’s talk about our Try methods. These methods are a staple for error handling in C#. They return a bool indicating success or failure and an out parameter to return a value. When dealing with nullable reference types, however, they can introduce some friction with the compiler’s nullability checks.

Considering the following method signature:

public bool TryBlahem(out string? blahem) { ... }

When you use it like this:

if (!TryBlahem(out string? blahem))
{
  // We typically expect the blahem variable to be null here
  return;
}

// We typically expect blahem to never be null here, but we have to use the null forgiving operator to keep the compiler happy

You’d expect blahem to be null if TryBlahem returns false, and non-null if it returns true. However, the compiler requires us to use the null-forgiving operator to satisfy its nullability checks, which can be a bit of an eyesore and somewhat defeat the purpose of null checks.

Solution

Here’s where the MaybeNullWhen attribute comes into play. By decorating the out parameter with this attribute, we provide the compiler with the necessary information to understand the relationship between the return value and null state of the parameter.

Here’s how you can use it:

public bool TryBlahem([MaybeNullWhen(false)] out string? blahem) { ... }

With this in place, the compiler now understands that blahem could be null when TryBlahem returns false, and not null when it returns true. This allows us to avoid using the null-forgiving operator:

if (!TryBlahem(out string? blahem))
{
    // We still typically expect the blahem variable to be null here
    return;
}

// Compiler knows blahem will not be null here so no need to use null forgiving operator

This small change makes the code cleaner and the developer’s intent clearer. No more unnecessary null-forgiving operators!

But that’s not all. The C# language has a variety of attributes that can help with nullable analysis. These attributes provide hints to the compiler about how we expect our code to behave in terms of nullability. You can find a comprehensive list and their descriptions at the official Microsoft documentation: Nullable static analysis attributes.

Conclusion

Incorporating these attributes into your code can reduce the number of warnings and improve the clarity of your nullability contracts. It’s a small investment into your code quality that can pay off in readability and maintainability.

So next time you find yourself reaching for the null-forgiving operator, consider whether onme of these attributes could make your intention clearer and your code cleaner.