1. The NullReferenceException Problem
Pretty much every beginner (and not just beginners) C# programmer has run into this scary message at least once:
System.NullReferenceException: Object reference not set to an instance of an object.
Here's a classic example:
string hello = null;
Console.WriteLine(hello.Length); // BOOM! NullReferenceException
The idea is simple: you're trying to access an object that doesn't exist. That is, your variable points to null — an empty spot instead of an object.
Why is this mistake so easy to make?
Because most reference types in C# have historically been able to take the value null. Programmers often forgot to check if a variable actually points to an object, and as a result, got hit with the infamous NullReferenceException.
If programmers got a dollar for every NullReferenceException, they'd have written their own operating system by now.
Why is this a problem?
Earlier, we learned how to make values of type int? or double? equal to null — for cases when we need to explicitly say "there's no value" (like a field in a database that might be empty).
But reference types (like string, any classes, arrays, etc.) have always been able to be null. That's been the case since C# was born. It's convenient, but also dangerous — because the language never forced us to think: "Could this reference be empty?"
2. Evolution: Nullable Reference Types (NRT)
Five years ago, a new feature was introduced that totally flipped the way we deal with NullReferenceException — Nullable Reference Types (NRT).
string s = "hello"; // not-nullable reference
string? maybe = null; // nullable reference
Main idea:
- Strictly separate reference variables that definitely can't be null from those that can be empty.
- Start helping the programmer see potential null problems right at compile time, not at runtime (when it's already too late).
In newer versions of C#, the approach to declaring reference variables changed: by default, you can't assign null unless you explicitly allow it.
It's that simple — just one ? character, and the meaning of your variable changes instantly!
3. Turning on Nullable Reference Types
By default, this strict syntax is turned off in most projects, so old code doesn't break. But modern project templates in Visual Studio, Rider, and .NET CLI already create projects with NRT enabled — or at least strongly recommend you do it.
To check if NRT is enabled in your project, look for this line in your .csproj file:
<Nullable>enable</Nullable>
If you don't see this line, you can add it manually — it's safe.
How does this affect your code?
- If NRT is off (old behavior): string s = null; — no errors, nobody cares.
- If it's on: the compiler will complain if you try to shove null into something that shouldn't be null.
4. NRT Examples
Simple example
#nullable enable // This line turns on NRT checks for this file
string notNullable = "Hello";
string notNullable2 = null; // COMPILATION ERROR!
string? nullableString = null; // All good, we explicitly allowed null
In the first line, we declared a string that always has to point to a real object.
In the third — we declared a string that can be null.
Checking for null
void PrintLength(string? s)
{
// The compiler will complain: "But what if s == null?"
Console.WriteLine(s.Length);
// This way - it's fine
if (s != null)
{
Console.WriteLine(s.Length);
}
}
The compiler now helps you remember to check!
Compiler warnings
If you ignore the warning and still try to access a nullable variable without checking — you'll get a new (and super useful!) compiler warning. It's not an error (your program will still build), but the yellow "lightbulb" is a hint: "You sure about this, buddy?"
Comparing modes:
| C# Type | Can be null? | Legacy mode (before NRT) | NRT mode (#nullable enable) |
|---|---|---|---|
| int | No | No | No |
| int? | Yes | Yes | Yes |
| string | Yes | No (always allowed) | No (not allowed by default) |
| string? | Yes | No | Yes |
5. Tips: How and Why to Use NRT
- Fewer bugs: Less random crashes because of null, more happiness for devs and users.
- Clearer code: It's obvious right away what can be empty and what always has to be filled in.
- Compiler help: Time to admit it — it's really trying to help us! NRT warnings are a goldmine for catching potential bugs.
Where this is a lifesaver
- In big projects where lots of people work on the same codebase.
- In APIs and public libraries — to make it clear to others what they can and can't do.
- Anywhere reliability matters (like banking apps, medical systems, etc.)
6. Typical mistakes and gotchas
- "Forgot the ?"
You assign null to a regular string (string s = null;) — and the compiler complains, because by default now a regular string can't be null. - "Went overboard with ?"
You make every variable string? just so the compiler doesn't yell at you. The whole point is to carefully mark where empty values are actually allowed. - "Misunderstood the warning"
You ignore the warning, then get hit with a NullReferenceException where you thought the compiler had your back.
GO TO FULL VERSION