0Pricing
C# Academy · 课时

将代码库迁移到 NRT

采用分阶段迁移策略:启用警告、为 API 添加注解、修复问题,并避免误报。

将代码库迁移到 NRT 是 CoddyKit 上的免费 C# Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。

迁移挑战

在现有代码库中启用 NRT 通常会产生数百条警告。一次性全部修改的做法风险很高。相反,请采用分阶段迁移:逐步启用警告,逐个文件修复,并始终保持推进。

第 1 步:仅启用警告

请先使用 <Nullable>warnings</Nullable>,而不是 enable。这样会启用警告,但不会将未添加注解的代码视为错误,是一个安全的起点。

<!-- 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 -->

第 2 步:按文件启用

处理每个文件时,在文件顶部添加 #nullable enable。这样可以将更改限制在当前正在编辑的文件中,使代码审查更易于管理。

#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 unchecked

对警告进行分类

警告分为两类:可以安全抑制的警告(ORM 实体、DI 注入字段)和真实错误(实际为 null 的值被解引用)。在抑制任何警告前,请先区分这两类情况。

// 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!
}

修复构造函数警告 CS8618

当不可为 null 的属性未在构造函数中设置时,就会触发 CS8618。首选的修复方式是在构造函数中要求提供该值。只有对于由框架设置的值,才应使用 = null!。

// 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;
    }
}

处理旧版 API

第三方或旧版 API 可能没有添加注解。它们的返回类型是未明确标注的(既不是可空类型,也不是不可空类型)。请将其结果赋给可空变量,以明确表达意图。

// 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 versions

使用 #pragma 抑制特定警告

当某条警告确实是误报,而 = null! 显得过于嘈杂时,请使用 #pragma warning disable,并将其作用范围限制在具体代码行。

// 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 Core

将 NRT 警告视为错误

修复文件中的所有警告后,添加 <WarningsAsErrors>Nullable</WarningsAsErrors>(或使用 CI 强制执行),以防止问题回归——任何新的可空性问题都会导致构建失败。

<!-- After full migration: treat nullable warnings as build errors -->
<PropertyGroup>
  <Nullable>enable</Nullable>
  <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
  <!-- Or selectively: -->
  <!-- <WarningsAsErrors>CS8600;CS8602;CS8603</WarningsAsErrors> -->
</PropertyGroup>

为公共 API 添加注解

当其他人使用您的库时,NRT 注解就会成为公共 API 契约的一部分。如果结果可能为 null,请返回 T?;如果结果有明确保证,请返回 T。

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);
}

迁移指标与跟踪

您可以通过统计包含 #nullable enable 的文件数量,或在 CI 中运行 dotnet build 2>&1 | grep CS86 来跟踪进度。请为整个项目的迁移设定目标日期。

# 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 sprint

快速检查

对不可为 null 的属性使用 = null! 赋值表达了什么含义?

总结:迁移到 NRT

关键要点:

  • 采用分阶段迁移:警告模式 → 按文件启用 → 全局启用
  • 区分真实错误(修复它们)与 ORM/DI 模式(使用 = null!)
  • 通过在构造函数中要求提供值来修复 CS8618,而不是抑制警告
  • 将旧版 API 的结果赋给 T? 变量,以明确表达意图
  • 在 CI 中将可空性警告视为错误,以防止问题回归
  • 带注解的公共 API 会为使用者提供清晰的契约

常见问题解答

「将代码库迁移到 NRT」课时是免费的吗?

是的 — 「将代码库迁移到 NRT」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。

「将代码库迁移到 NRT」这节课中我会学到什么?

采用分阶段迁移策略:启用警告、为 API 添加注解、修复问题,并避免误报。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 C# Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「将代码库迁移到 NRT」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 C# Academy 课中编写并运行代码吗?

能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 启用并理解 NRT
  2. 注解:?、!、MaybeNull 与 NotNull
  3. 空值条件与空值合并运算符
  4. 将代码库迁移到 NRT
← 返回 C# Academy