1. What is a NullReferenceException?

A NullReferenceException occurs at runtime when your code attempts to access a member (such as a property, method, or field) on a variable whose value is null (meaning it doesn’t point to an object in memory).

string s2 = null;
// Throws System.NullReferenceException: Object reference not set to an instance of an object.
int length = s2.Length; 

2. Nullable Reference Types & Why They Were Needed

Why Were They Needed?

Historically in C#, all reference types (like string, User, or custom classes) could always be null. Tony Hoare, who invented the null reference in 1965, famously called it his “billion-dollar mistake” because it led to countless runtime exceptions, application crashes, and defensive if (x != null) boilerplate checks scattered across codebases.

To solve this, Microsoft introduced Nullable Reference Types (starting in C# 8, fully mature in .NET 6/7/8) which shift the burden of catching nulls from runtime to compile-time.

How It Changes Things

With <Nullable>enable</Nullable> turned on, the C# type system is split into two states:

  1. Non-nullable reference types (default): The compiler assumes a reference type never holds null. If you try to assign null to it, the compiler throws a warning (CS8600).

  2. Nullable reference types: If you explicitly want a variable or property to accept null, you must opt-in by adding a question mark (?) to the type. Also

    1. Ensure your .csproj file has <Nullable>enable</Nullable>:

       <PropertyGroup>
       	<Nullable>enable</Nullable>
       </PropertyGroup>
    2. Without <Nullable>enable</Nullable>, C# runs in its legacy mode where the question mark (?) syntax is largely ignored, and all reference types default to being able to accept null silently.

    3. With <Nullable>enable</Nullable> turned on, the compiler actively enforces the rules, making regular reference types strict and forcing you to explicitly use User? when you actually want to permit null values.

Can you still do User user = null;?

No, not directly. If you have nullable context enabled, writing:

User user = null; // Will trigger compiler warning CS8600

Because the compiler treats User as strictly non-nullable, assigning null generates a warning.

To allow a reference type to hold null, you must explicitly declare it as nullable using the ? suffix:

User? user = null; // Perfectly valid!

This tells both the compiler and other developers that this variable is intentionally allowed to be null, forcing you to handle it safely before accessing its members.

3. Best Practices & Safe Handling Operators

A. The Null-Conditional Operator (? and `?[)

The ?. Operator (Member Access)

Safely attempts to access a member. If the object is null, it short-circuits and evaluates to null instead of throwing an exception.

string? s2 = null;
// Evaluates to 'int?' (nullable int), returning null instead of crashing
int? length = s2?.Length; 

Used when you want to access a property or method on an object that might be null. Instead of throwing a NullReferenceException, it stops evaluating and returns null.

class User 
{
    public string Name { get; set; } = "";
}
 
User? user = null;
 
// Without null-conditional, this would crash immediately:
// int length = user.Name.Length; 
 
// With '?.', it safely returns null instead of throwing an exception:
int? nameLength = user?.Name?.Length; 
 
Console.WriteLine(nameLength is null); // Output: True

The ?[ Operator (Index Access)

Used when you want to retrieve an element from a list, array, or dictionary by its index or key, but the collection itself might be null.

// A list that is currently uninitialized (null)
List<string>? items = null;
 
// Without '?[', trying to index items[0] would throw a NullReferenceException
// string firstItem = items[0];
 
// With '?[', it safely short-circuits and returns null
string? firstItem = items?[0];
 
Console.WriteLine(firstItem == null); // Output: True

It also works great with dictionaries:

Dictionary<int, string>? lookup = null;
 
// Safely attempts to look up a key from a potentially null dictionary
string? result = lookup?[42]; 

Both operators return a nullable type (e.g., int? or string?), which forces you to handle the potential null result gracefully before using it further down your code.

B. The Null-Coalescing Operator (??)

Provides a fallback default value if the expression on the left evaluates to null.

string? s2 = null;
// If s2 is null, it falls back to "Default Text"
string safeString = s2 ?? "Default Text"; 

C. The Null-Coalescing Assignment Operator (??=)

Assigns a value to a variable only if that variable is currently null.

string? s2 = null;
// Assigns "Initialized" to s2 because it was null
s2 ??= "Initialized"; 

D. Argument Validation (.NET 8 Helpers)

To prevent bad inputs from propagating through your codebase, validate parameters immediately at the entry point of your methods.

  • ArgumentNullException.ThrowIfNull: Introduced to simplify guard clauses. Instead of writing verbose if (param == null) blocks, you can do:

    public void ProcessData(Client client)
    {
        ArgumentNullException.ThrowIfNull(client);
        // Safe to use client here
    }

E. The Forgiving Operator (!)

If you are 100% certain a variable isn’t null at a specific point, but the static analyzer thinks it might be, you can use the null-forgiving (or suppressor) operator (!):

// Tells the compiler: "Trust me, this element is not null here."
string firstItem = items.First()!; 

Use this sparingly, as abusing it defeats the safety guarantees of Nullable Reference Types.

4. Summary Table of Operators

OperatorNamePurposeExample
?Nullable TypeExplicitly allows a reference type to hold null.User? user = null;
?.Null-ConditionalSafely accesses members without crashing on null.int? len = user?.Id;
??Null-CoalescingProvides a fallback value if left side is null.string name = user?.Name ?? "Guest";
??=Coalescing AssignAssigns value only if the variable is currently null.status ??= "Active";