Skip to content
Nullable Reference Types
step 1/6

Reading — step 1 of 6

Learn

~2 min readModern C#

C# 8 introduced nullable reference types — making nullability part of the type system. Like Kotlin or TypeScript's null safety, this catches whole classes of NullReferenceException bugs at compile time.

Enabling NRT

In .csproj:

<Nullable>enable</Nullable>

Or per-file:

#nullable enable

With NRT enabled, string means "non-null string," and string? means "nullable string."

The basic distinction

string name = null;          // ⚠ warning — can't assign null to non-nullable
string? maybeName = null;    // ✓ OK — nullable

string g = maybeName.ToUpper();  // ⚠ warning — possible NullReferenceException

The compiler tracks whether each value might be null. Calling methods on something string?-typed without checking is a warning.

Null-conditional and null-coalescing

string? maybeName = GetName();
int? len = maybeName?.Length;             // null if maybeName is null
int length = maybeName?.Length ?? 0;       // default if null

// Throw on null:
string nonNull = maybeName ?? throw new ArgumentException("name required");

// Null-forgiving operator (use sparingly — promises non-null to the compiler)
int forced = maybeName!.Length;            // suppresses the warning

The ! operator says "trust me, this isn't null." It does NOT add a runtime check — if you're wrong, you crash.

Null-checking and flow analysis

After you check, the compiler narrows the type:

string? name = GetName();
if (name != null)
{
    Console.WriteLine(name.Length);   // ✓ no warning — narrowed to non-null
}

// Also works:
if (name is null) return;
Console.WriteLine(name.Length);   // ✓ narrowed

// And:
name ??= "default";
Console.WriteLine(name.Length);   // ✓ guaranteed non-null after ??=

Nullable annotations on parameters

public void Greet(string name)             // parameter must not be null
public void Greet(string? name)            // null is acceptable
public User? FindUser(int id)              // result might be null
public User FindUser(int id)               // result guaranteed non-null

The annotations document intent AND let the compiler enforce it.

Generic constraints

public T? FirstOrDefault<T>(IEnumerable<T> items)
    where T : class           // T is a non-nullable reference type → T? makes sense
{
    foreach (var item in items) return item;
    return null;
}

On this platform's grader (C# 7)

The string? annotation and #nullable enable are C# 8 — our grader runs Mono's C# 7 compiler, where they won't compile. In C# 7 every reference type is implicitly nullable (exactly the problem NRT was invented to fix!), so your exercise handles a plain string that may be null. The guard tools are identical in C# 7: string.IsNullOrWhiteSpace, ??, and ?..

Common mistakes

  • Sprinkling ! everywhere to silence warnings — defeats the purpose. Treat ! like a TODO.
  • Forgetting that T? for value types is Nullable<T> — for int, int? is Nullable<int>, a different type. For reference types it's just an annotation.
  • Mixing nullable and non-nullable code — start with <Nullable>enable</Nullable> for new code; use #nullable enable per-file in existing code.
  • Trusting external libraries' annotations blindly — sometimes wrong; prefer defensive checks at API boundaries.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…