Fundamentals – Part 4

07/29/2025
2 minute read

Nullable Reference Types

Nullable reference types (NRTs) have been in C# since version 8, and at first glance, the concept seems simple: Just turn on the feature and use ? to indicate a nullable reference. But in real-world code, especially in APIs, libraries, and layered architectures, it’s not enough. In this post, we’ll explore:

  • What NRTs actually do
  • Where they fall short
  • How to guide the compiler using attributes
  • And how to use NRTs to write safer, clearer code

Step 1: Enabling Nullable Reference Types

To turn on NRTs, add this to your project file:

<Nullable>enable</Nullable>

Now, the compiler will warn you if you dereference a variable that might be null.

string? name = GetName(); // nullable
Console.WriteLine(name.Length); // ⚠️ Warning: possible null reference

But what happens when things get more complex?


Step 2: Where Things Get Tricky

public string GetValue(bool isValid)
{
    if (isValid)
	{
        return "Hello";
	}
    return null; // ⚠️ Warning
}

Or in methods where nullability depends on external logic:

string? GetNameFromDb() => ...;

void GreetUser()
{
    var name = GetNameFromDb();

    if (!string.IsNullOrEmpty(name))
    {
        Console.WriteLine($"Hello {name}"); // ⚠️ Still warns
    }
}

Despite our null check, the compiler can’t prove name is not null here.

Step 3: Use Attributes to Help the Compiler

C# provides nullable annotations in System.Diagnostics.CodeAnalysis to inform the compiler of intent and flow.

[NotNullWhen(true)] Used on method parameters to tell the compiler a value will be non-null when the condition is true.

bool TryGetName([NotNullWhen(true)] out string? name)
{
    name = "Moran";
    return true;
}
if (TryGetName(out var result))
{
    Console.WriteLine(result.Length); // ✅ No warning
}

[MaybeNull] Use when a method returns a non-nullable type but might return null anyway, Without this, the compiler expects a guaranteed non-null string.

[return: MaybeNull]
public string GetCachedItem()
{
    return null;
}

[MemberNotNull] Use in methods that ensure one or more class members are not-null after execution, Now you can safely use _connection after calling Initialize().

private string? _connection;

[MemberNotNull(nameof(_connection))]
void Initialize()
{
    _connection = "Ready!";
}

When to Use These Attributes?

Use them when:

  • You’re implementing custom TryX patterns
  • You initialize members conditionally
  • You return null for performance reasons
  • Your API has known behaviors the compiler can’t infer

Final Thoughts

Nullable reference types are one of the most misunderstood features in C# — not because they’re hard, but because they require you to think like the compiler. By combining the ? operator with flow-aware attributes, you give the compiler the tools it needs to truly help you prevent bugs.

An error has occurred. This application may no longer respond until reloaded. Reload x