Skip to Content
Course content

17: Nullable Reference Types

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you've just turned on Nullable Reference Types in your project settings, you might be under the impression that you've just installed a "Null-Proof Shield" around your code. I see this all the time with developers moving from older versions of C#: the belief that marking a type as non-nullable actually prevents it from being null at runtime.

The myth that non-nullable means "impossible to be null"

Let's get this straight right away: string Name is not fundamentally different from string? Name when your code is actually running. The "non-nullable" part is a contract for the compiler, not a physical constraint on the memory.

Look at this snippet. I'm telling the compiler that Address can never be null, but I'm going to force it to be null anyway:

public class UserProfile
{
    public string Address { get; set; } = "Unknown";
}

// ... somewhere else in the code ...
UserProfile profile = new UserProfile();
// This is a dirty trick, but it shows the point
profile.Address = null!; 

Console.WriteLine(profile.Address.Length); // CRASH! NullReferenceException

The ! (null-forgiving operator) tells the compiler, "Shut up, I know what I'm doing." Because the compiler trusts you, it stops warning you. But the runtime doesn't care about your contract. It just sees a null reference and throws the same NullReferenceException we've been fighting since the 90s. The "shield" is just a set of warnings during development.

Turning warnings into a safety contract

The real power of Nullable Reference Types isn't that they stop nulls, but that they force you to be honest about where nulls might come from. Instead of guessing if a method might return null, the method signature tells you explicitly.

If I change a property to string?, I'm admitting: "This value might be missing." Once I do that, the compiler becomes a very helpful nag. It won't let me use that variable without checking it first.

public class Order
{
    public int OrderId { get; set; }
    public string? DiscountCode { get; set; } // Honest: not every order has a code
}

var myOrder = new Order();
// Console.WriteLine(myOrder.DiscountCode.Length); // The compiler flags this as a warning!

if (myOrder.DiscountCode != null)
{
    Console.WriteLine(myOrder.DiscountCode.Length); // Now the compiler is happy.
}


I personally love this because it moves the "debugging" phase from runtime (where the app crashes for the user) to compile-time (where I just have to fix a yellow squiggly line in my IDE).

Dealing with the "I know more than the compiler" moments

You'll inevitably run into situations where the compiler thinks something could be null, but you know—based on the logic of your app—that it absolutely isn't. This happens a lot with unit tests or legacy APIs.

  • The Null-Forgiving Operator (!): As shown earlier, appending ! tells the compiler to ignore the null warning. Use this sparingly. If you find yourself using it everywhere, you're probably just lying to yourself to make the warnings go away.
  • The Null-Coalescing Operator (??): Instead of forcing a non-null type, just provide a fallback. string displayCode = myOrder.DiscountCode ?? "NO_CODE"; is infinitely safer than using !.

The goal here isn't to eliminate null entirely—that's nearly impossible in C#—but to make the nulls explicit. When you see a ?, you know you need a guard clause. When you don't, you can breathe a little easier.




📋 Practical Task

Fixing the Broken Customer Database Loader

You've inherited a piece of code that loads customer data from a mock database. The project has Nullable Reference Types enabled, but the previous developer used the ! operator to silence all the warnings rather than actually handling the nulls. This is causing random crashes in production.

Your Task: Refactor the following code to remove the dangerous null-forgiving operators and implement proper null handling using either if checks or the null-coalescing operator (??).

public class Customer
{
    public string Name { get; set; } = string.Empty;
    public string? Email { get; set; } // Email is optional
}

public class CustomerService
{
    public void PrintCustomerSummary(Customer? customer)
    {
        // BUG: This will crash if customer is null because of the '!' operator
        Console.WriteLine($"Processing: {customer!.Name}");

        // BUG: This will crash if Email is null because of the '!' operator
        Console.WriteLine($"Email: {customer!.Email!.ToLower()}");
    }
}

// Test Case
var service = new CustomerService();
service.PrintCustomerSummary(null); // This currently crashes

Requirements:

  • Ensure PrintCustomerSummary can handle a null customer without crashing.
  • Ensure the Email is handled gracefully if it is null (e.g., print "No email provided").
  • Remove all instances of the ! operator.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.