Migrating a Codebase to NRT
Apply a phased migration strategy: enable warnings, annotate APIs, fix issues, and avoid false-positives.
Migrating a Codebase to NRT is a free C# Academy lesson on CoddyKit — lesson 4 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 Migration Challenge
Enabling NRT on an existing codebase typically produces hundreds of warnings. A big-bang approach is risky. Instead, use a phased migration: enable warnings incrementally, fix them file by file, and never lose momentum.
Step 1: Enable Warnings Only
Start with <Nullable>warnings</Nullable> instead of enable. This activates warnings without treating non-annotated code as errors — a safe starting point.
<!-- Phase 1: warnings only, no breaking change -->
<PropertyGroup>
<Nullable>warnings</Nullable>
</PropertyGroup>
<!-- Phase 2: full enable per file as you migrate -->
<!-- Phase 3: switch to enable globally when done -->Step 2: Enable Per File
Add #nullable enable at the top of each file as you work on it. This confines changes to files you're actively editing, making reviews manageable.
#nullable enable
// Now this file has full NRT analysis
public class OrderService
{
private readonly IOrderRepository _repo;
// Compiler now warns about uninitialized non-nullable fields,
// unsafe dereferences, and assignment to non-nullable
public OrderService(IOrderRepository repo) => _repo = repo;
}
// Other files without #nullable enable are still uncheckedCategorizing Warnings
Warnings fall into two categories: safe to suppress (ORM entities, DI-injected fields) and real bugs (actually null values being dereferenced). Distinguish these before suppressing anything.
// Category 1: safe to suppress with null!
// EF Core navigation properties — set by EF, never null in practice
public class Order
{
public Customer Customer { get; set; } = null!;
}
// Category 2: real bug — must fix
public string GetFullName()
{
return FirstName + " " + LastName; // LastName was string? -- BUG!
}Fixing Constructor Warning CS8618
CS8618 fires when a non-nullable property isn't set in the constructor. The preferred fix is to require it in the constructor. Use = null! only for framework-set values.
// BEFORE (CS8618)
public class Product
{
public string Name { get; set; } // warning
public Category Category { get; set; } // warning
}
// AFTER — constructor required:
public class Product
{
public string Name { get; set; }
public Category Category { get; set; }
public Product(string name, Category category)
{
Name = name;
Category = category;
}
}Handling Legacy APIs
Third-party or legacy APIs may not be annotated. Their return types are oblivious (neither nullable nor non-nullable). Assign their results to nullable variables to be explicit.
// Legacy API returns 'string' but might be null (oblivious type)
string? legacyResult = OldLibrary.GetValue(); // store as nullable
if (legacyResult is null) return;
// Or convert at the boundary:
string safe = OldLibrary.GetValue() ?? "";
// For third-party types, check if they have NRT annotations:
// NuGet packages often add nullable annotations in newer versionsUsing #pragma to Suppress Specific Warnings
When a warning is genuinely a false positive and = null! feels too noisy, use #pragma warning disable scoped to the specific line.
// Suppress for a specific case with explanation:
#pragma warning disable CS8618 // ORM populates this via reflection
public DbSet<Product> Products { get; set; }
#pragma warning restore CS8618
// Or inline with a comment:
public DbSet<Order> Orders { get; set; } = null!; // set by EF CoreTreating NRT Warnings as Errors
Once all warnings are fixed in a file, add <WarningsAsErrors>Nullable</WarningsAsErrors> (or use CI enforcement) to prevent regression — any new nullable issue fails the build.
<!-- After full migration: treat nullable warnings as build errors -->
<PropertyGroup>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<!-- Or selectively: -->
<!-- <WarningsAsErrors>CS8600;CS8602;CS8603</WarningsAsErrors> -->
</PropertyGroup>Annotating Public APIs
When your library is consumed by others, NRT annotations become part of your public API contract. Return T? when the result can be null; return T when it's guaranteed.
public interface IProductService
{
// Contract: FindById MAY return null, GetById never does
Product? FindById(int id);
Product GetById(int id); // throws if not found
// Collection: never null (may be empty)
IReadOnlyList<Product> GetAll();
// String: may be empty but not null
string GetSummary(int id);
}Migration Metrics and Tracking
Track progress by counting files with #nullable enable or by running dotnet build 2>&1 | grep CS86 in CI. Set a target date for full project migration.
# Count NRT warnings in current build
dotnet build 2>&1 | grep -c 'CS860[0-9]\|CS861[0-9]\|CS862[0-9]'
# List files still missing #nullable enable
grep -rL '#nullable enable' src/ --include='*.cs'
# Track in CI: fail if warning count increases
# Set a budget: warnings <= N, where N decreases each sprintQuick Check
What does the = null! assignment on a non-nullable property communicate?
Recap: Migrating to NRT
Key takeaways:
- Use phased migration: warnings mode → per-file enable → global enable
- Distinguish real bugs (fix them) from ORM/DI patterns (use
= null!) - Fix CS8618 by requiring values in constructors, not by suppressing
- Assign legacy API results to
T?variables to be explicit - Treat nullable warnings as errors in CI to prevent regression
- Annotated public APIs become clear contracts for consumers
Frequently asked questions
Is the “Migrating a Codebase to NRT” lesson free?
Yes — the full text of “Migrating a Codebase to 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 “Migrating a Codebase to NRT”?
Apply a phased migration strategy: enable warnings, annotate APIs, fix issues, and avoid false-positives. 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 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Migrating a Codebase to 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.