Enabling & Understanding NRT
Enable nullable context, understand nullable vs non-nullable reference types, and read compiler warnings.
Enabling & Understanding NRT is a free C# Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the C# Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
The Billion Dollar Mistake
Tony Hoare (inventor of null) called it his 'billion dollar mistake'. In C#, any reference type could be null by default, making NullReferenceException the most common runtime crash. Nullable Reference Types (NRT) fix this at the compiler level.
Enabling the Nullable Context
Enable NRT globally in your project file or per-file with directives. Once enabled, the compiler treats reference types as non-nullable by default.
<!-- In .csproj — enable for the whole project -->
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
// Or per-file:
#nullable enable
// ... code with NRT warnings
#nullable disable
// ... code without NRT warningsNon-Nullable vs Nullable Reference Types
With NRT enabled: string means the value is never null, string? means it might be null. The compiler warns if you assign null to a non-nullable or dereference a nullable without a check.
#nullable enable
string nonNull = "Hello"; // OK
string? maybeNull = null; // OK — explicitly nullable
nonNull = null; // WARNING: CS8600
Console.WriteLine(maybeNull.Length); // WARNING: CS8602 — may be null
// Fix:
if (maybeNull is not null)
Console.WriteLine(maybeNull.Length); // safeFlow Analysis
The compiler performs flow analysis — it tracks null state through conditionals and branches, suppressing warnings when it can prove a value is not null.
string? name = GetName();
// After null check, name is treated as non-null:
if (name is not null)
Console.WriteLine(name.Length); // no warning
// Same with early return:
if (name is null) return;
Console.WriteLine(name.Length); // no warning — null was returned
// Pattern matching:
if (name is string s)
Console.WriteLine(s.ToUpper()); // no warningNullable Warnings: CS8600–CS8629
Key NRT warnings to understand: CS8600 (assigning null to non-nullable), CS8602 (dereference of possibly-null), CS8603 (returning null for non-nullable), CS8618 (uninitialized non-nullable field).
#nullable enable
public class Order
{
public string CustomerName { get; set; } // CS8618: not initialized
public string? TrackingNumber { get; set; } // OK — nullable
public Order(string name)
{
CustomerName = name; // now initialized — CS8618 gone
}
}
string? s = GetValue();
Console.WriteLine(s.Length); // CS8602: s might be nullInitializing Non-Nullable Fields
CS8618 fires when a non-nullable field or property isn't set in the constructor. Solutions: initialize in the declaration, require it via constructor, or use the null-forgiving operator for DI/ORM patterns.
// Option 1: Initialize in declaration
public string Name { get; set; } = "";
// Option 2: Require via constructor
public class Product
{
public string Name { get; }
public Product(string name) => Name = name;
}
// Option 3: null-forgiving for ORM entities
public string Name { get; set; } = null!;
// null! tells compiler "trust me, this will be set by EF Core"Nullable in Generic Types
Generic type parameters can be constrained to non-nullable. A where T : notnull constraint ensures T can never be null.
// Without constraint: T could be nullable
public T? Find<T>(int id) { ... }
// With notnull: T must be a non-nullable type
public T FindRequired<T>(int id) where T : notnull
{
var result = InternalFind<T>(id);
return result ?? throw new KeyNotFoundException();
}
// T? is meaningful only when T is known to be non-nullable
public T? FindOrDefault<T>(int id) where T : class { ... }Nullable in Interfaces and Overrides
When implementing an interface or overriding a method, nullability annotations must match. The compiler checks that implementations are at least as non-null as the interface declares.
public interface IRepository<T>
{
T? FindById(int id); // may return null
T GetOrThrow(int id); // never null
}
// Implementation:
public class ProductRepo : IRepository<Product>
{
public Product? FindById(int id) => _db.Find(id);
public Product GetOrThrow(int id) =>
_db.Find(id) ?? throw new KeyNotFoundException();
}Nullable Value Types vs Nullable Reference Types
Don't confuse int? (Nullable<int> — a value type wrapper that existed before C# 8) with string? (NRT annotation — compile-time only, no runtime overhead).
// Nullable value type (runtime Nullable<int>)
int? age = null;
bool hasValue = age.HasValue;
int value = age.GetValueOrDefault();
// Nullable reference type (compile-time annotation only)
string? name = null;
// string? has NO runtime wrapper — it's just a compile-time hint
// null check is still needed at runtime
if (name is not null)
Console.WriteLine(name.ToUpper());Suppressing Warnings with !
The null-forgiving operator (!) suppresses a specific nullable warning when you know better than the compiler. Use it sparingly and document why it's safe.
// Use ! when you know the value cannot be null
var user = _db.Users.FirstOrDefault(u => u.Email == email);
var name = user!.Name; // user is guaranteed by business logic
// EF Core navigation properties set by the framework:
public class Order
{
public Customer Customer { get; set; } = null!; // set by EF Core
}Real-World: Nullable API Response
An API wrapper where fields may or may not be present in the JSON response — nullable annotations communicate intent clearly.
#nullable enable
public class WeatherResponse
{
public string City { get; set; } = ""; // always present
public double Temperature { get; set; } // always present
public string? Description { get; set; } // optional field
public string? AlertMessage { get; set; } // only when there's an alert
}
// Consumer:
var weather = await api.GetWeatherAsync("London");
Console.WriteLine(weather.City); // safe
Console.WriteLine(weather.Description?.ToUpper() ?? "N/A"); // safe null-handlingQuick Check
What is the runtime overhead of Nullable Reference Types (string?) compared to regular reference types (string)?
Recap: Enabling & Understanding NRT
Key takeaways:
- Enable globally with
<Nullable>enable</Nullable>in .csproj string= never null;string?= may be null- Flow analysis tracks null state through branches and returns
- NRT annotations are compile-time only — zero runtime overhead
- CS8618: initialize non-nullable fields in constructor or use
= null!for ORM - Use ! sparingly — document why the null-forgiving is safe
Frequently asked questions
Is the “Enabling & Understanding NRT” lesson free?
Yes — the full text of “Enabling & Understanding NRT” is free to read here on the web, and the C# Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the C# Academy course, upgrade to CoddyKit PRO.
What will I learn in “Enabling & Understanding NRT”?
Enable nullable context, understand nullable vs non-nullable reference types, and read compiler warnings. You practise C# Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start C# Academy?
No prior experience is required. C# Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Enabling & Understanding NRT” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this C# Academy lesson?
Yes. Every C# Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Enabling & Understanding NRT
- Annotations: ?, !, MaybeNull & NotNull
- Null-Conditional & Null-Coalescing Operators
- Migrating a Codebase to NRT