csharp-nullable-reference-types
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseC# Nullable Reference Types
C# 可空引用类型
When to Use
何时使用
- Introducing nullable reference types (NRT) into a codebase that has not yet adopted them
- Writing or refactoring C# code that uses / nullable annotations
T? - Annotating APIs with nullable attributes
System.Diagnostics.CodeAnalysis - Designing public/internal APIs where nullability contracts matter
- Wrapping unannotated or legacy APIs so downstream callers still benefit from NRT
- Reviewing code for correct null-state analysis, guard helpers, and the keyword
field
- 向尚未采用可空引用类型(NRT)的代码库引入 NRT
- 编写或重构使用 / 可空注解的 C# 代码
T? - 使用 可空性属性为 API 添加注解
System.Diagnostics.CodeAnalysis - 设计对可空性契约有要求的 public/internal API 和模型
- 包装未添加注解或旧版 API,使下游调用方仍能受益于 NRT
- 审查代码中的空值状态分析、保护条件辅助方法和 关键字是否正确
field
Core Goals
核心目标
- Prevent at runtime by making null intent explicit in signatures.
NullReferenceException - Express contracts the type system cannot represent directly using the official nullable attributes.
- Adopt NRT incrementally in legacy codebases without a big-bang rewrite.
- 通过在签名中明确 null 意图,防止运行时出现 。
NullReferenceException - 使用官方可空性属性表达类型系统无法直接表示的契约。
- 在不进行一次性大规模重写的情况下,逐步在旧代码库中采用 NRT。
Core Nullability Model
核心可空性模型
Non-nullable vs nullable
不可空与可空
- — non-nullable reference. The compiler assumes instances are never
string; assigningnullor a maybe-null value produces a warning.null - — nullable reference. The variable may be
string?; the compiler requires a null check before dereference.null
csharp
string name = "Alice";
name = null; // Warning: assigning null to non-nullable.
string? nickname = null;
Console.WriteLine(nickname.Length); // Warning: possible null dereference.- — 不可空引用类型。编译器假定实例永不为
string;赋值null或可能为 null 的值会产生警告。null - — 可空引用类型。变量可能为
string?;解引用前,编译器要求进行 null 检查。null
csharp
string name = "Alice";
name = null; // Warning: assigning null to non-nullable.
string? nickname = null;
Console.WriteLine(nickname.Length); // Warning: possible null dereference.Null-state analysis (flow)
空值状态分析(流分析)
The compiler tracks whether a reference is definitely non-null or maybe null. Null checks and assignments update this state.
csharp
string? message = GetMessageOrNull();
if (message != null)
{
// message is definitely non-null in this block.
Console.WriteLine(message.Length);
}
// Outside the if, message is maybe null again.Introduce explicit null checks (, , pattern matching) before dereferencing nullable values. Narrow nullability early and keep the non-null state alive. Null-conditional assignment (C# 14) lets you write — the right side is evaluated only when the receiver is non-null.
if (x != null)is not nullcustomer?.Order = CreateOrder();编译器跟踪引用是确定为非 null还是可能为 null。Null 检查和赋值会更新此状态。
csharp
string? message = GetMessageOrNull();
if (message != null)
{
// message is definitely non-null in this block.
Console.WriteLine(message.Length);
}
// Outside the if, message is maybe null again.在解引用可空值之前,应引入显式 null 检查(、、模式匹配)。尽早收窄可空性,并保持非空状态。**Null-conditional assignment(C# 14)**允许编写 ——仅当接收对象非 null 时才会计算右侧。
if (x != null)is not nullcustomer?.Order = CreateOrder();Null-forgiving operator (!
)
!Null-forgiving operator(!
)
!x!x- Use only when a real invariant guarantees non-null and the compiler cannot see it.
! - Do not use as a general fix for warnings. Prefer refactoring control flow, adding attributes, or proper member initialization.
!
csharp
_customer = LoadCustomerFromOrm()!; // ORM guarantees this is not null in valid state.x!x- 仅当真实的不变量保证值非 null、但编译器无法识别时,才使用 。
! - 不要将 作为解决警告的通用手段。应优先重构控制流、添加属性或正确初始化成员。
!
csharp
_customer = LoadCustomerFromOrm()!; // ORM guarantees this is not null in valid state.Reorganize code before suppressing warnings
在抑制警告前重组代码
A successful guard clause or pattern match already creates a null-safe region in the current scope. Before adding , make nullable values cross a checked boundary once and keep the remaining code non-nullable:
!- narrow early with a guard clause or pattern match;
- copy nullable fields or properties to a local before checking, so repeated reads cannot change underneath the analysis;
- when a method has complex control flow, optionally move the non-null path into a local function or private method with non-nullable parameters;
- keep nullable handling at the boundary instead of spreading , repeated checks, or
T?through the implementation.!
csharp
public void Process(Order? order)
{
if (order?.Customer is not { } customer)
{
return;
}
// The pattern match already proved that customer is non-null here.
Console.WriteLine(customer.Name);
}Do not extract a function solely to satisfy nullable analysis. Use an explicit non-nullable function boundary when it also simplifies a large or branching implementation. Use only when a real external invariant cannot be represented through control flow, signatures, or nullable-analysis attributes.
!成功的 guard clause 或模式匹配会在当前作用域中创建 null-safe 区域。在添加 前,应让可空值仅跨越一次已检查边界,并使剩余代码保持不可空:
!- 使用 guard clause 或模式匹配尽早收窄可空性;
- 检查前先将可空字段或属性复制到局部变量,防止重复读取在分析期间发生变化;
- 当方法具有复杂控制流时,可选择将非空路径移入采用不可空参数的局部函数或私有方法;
- 将可空处理保留在边界,不要让 、重复检查或
T?扩散到实现内部。!
csharp
public void Process(Order? order)
{
if (order?.Customer is not { } customer)
{
return;
}
// The pattern match already proved that customer is non-null here.
Console.WriteLine(customer.Name);
}不要仅仅为了满足可空性分析而提取函数。仅当显式的不可空函数边界也能简化大型或分支较多的实现时,才使用这种方式。只有当真实的外部不变量无法通过控制流、签名或可空性分析属性表示时,才使用 。
!Project Configuration
项目配置
Enable NRT for new code:
xml
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>For legacy codebases, enable incrementally with file-level directives (, , , ). Treat nullable warnings as important; consider or treating nullable warnings as errors in new projects.
#nullable enable#nullable disable#nullable enable warnings#nullable enable annotationsCS86xxTreatWarningsAsErrorsSee nrt-migration-playbook-reference.md for the full incremental-adoption strategy, directive reference, legacy interop, and known static-analysis limitations (arrays, ).
#nullabledefault(struct)为新代码启用 NRT:
xml
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>对于旧代码库,使用文件级指令(、、、)逐步启用。将 可空性警告视为重要问题;考虑使用 ,或在新项目中将可空性警告视为错误。
#nullable enable#nullable disable#nullable enable warnings#nullable enable annotationsCS86xxTreatWarningsAsErrors有关完整的增量采用策略、 指令参考、旧版互操作和已知静态分析限制(数组、),请参阅 nrt-migration-playbook-reference.md。
#nullabledefault(struct)API Design Rules (Signatures)
API 设计规则(签名)
These rules apply to public and internal APIs and to models.
Parameters — if is not allowed, use a non-nullable type and add a runtime guard for public APIs:
nullcsharp
public void SendEmail(string recipient)
{
ArgumentNullException.ThrowIfNull(recipient);
// Implementation
}If is allowed and meaningful, use , document how is interpreted, and implement correct behavior.
nullT?nullnullReturn types — when the method never returns ; when it can legitimately return (callers must check, and the compiler enforces it).
CustomernullCustomer?nullcsharp
public Customer GetRequiredCustomer(Guid id); // Throws on failure.
public Customer? TryGetCustomer(Guid id); // Returns null on failure.Properties and fields — follow the same rules as parameters and return types. Non-nullable members must be initialized in constructors, via properties with object initializers, via -backed lazy properties, or via helpers annotated with .
requiredfield[MemberNotNull]csharp
public class Order
{
public required string Id { get; init; }
public required Customer Customer { get; init; }
public string? Comment { get; init; } // Optional.
}When contracts depend on input/output behavior, conditional behavior, or member initialization, apply the nullable attributes described in nullable-attributes-reference.md.
这些规则适用于 public 和 internal API 以及模型。
参数——如果不允许 ,请使用不可空类型,并为 public API 添加运行时保护:
nullcsharp
public void SendEmail(string recipient)
{
ArgumentNullException.ThrowIfNull(recipient);
// Implementation
}如果允许 且具有实际意义,请使用 ,说明如何解释 ,并实现正确的 行为。
nullT?nullnull返回类型——方法永不返回 时使用 ;可以合法返回 时使用 (调用方必须检查,编译器会强制执行)。
nullCustomernullCustomer?csharp
public Customer GetRequiredCustomer(Guid id); // Throws on failure.
public Customer? TryGetCustomer(Guid id); // Returns null on failure.属性和字段——遵循与参数和返回类型相同的规则。不可空成员必须通过构造函数初始化、通过带对象初始化器的 属性初始化、通过基于 的惰性属性初始化,或通过带 注解的辅助方法初始化。
requiredfield[MemberNotNull]csharp
public class Order
{
public required string Id { get; init; }
public required Customer Customer { get; init; }
public string? Comment { get; init; } // Optional.
}当契约依赖输入/输出行为、条件行为或成员初始化时,请应用 nullable-attributes-reference.md 中描述的可空性属性。
Public API compatibility for libraries
库的 public API 兼容性
Treat nullable annotations and nullable-analysis attributes as part of a shipped API contract. and have the same CLR type, so annotation-only changes are generally binary compatible, but they can be source breaking by introducing warnings for nullable-enabled consumers. Those warnings often become build failures when consumers treat warnings as errors.
TT?Review public nullability changes before release, especially:
- weakening an output from to
Tor addingT?;[MaybeNull] - tightening an input from to
T?or addingT;[DisallowNull] - changing generic constraints such as ,
class?, orclass;notnull - changing annotations or attributes on virtual members, interfaces, delegates, and implementations, where mismatches produce compiler warnings.
Adding annotations to a previously nullable-oblivious API can create the same source-compatibility problems. Compare the annotated surface with the last released version, test a nullable-enabled consumer, and document or version intentional source-breaking changes according to the library's compatibility policy.
将可空性注解和可空性分析属性视为已发布 API 契约的一部分。 和 具有相同的 CLR 类型,因此仅涉及注解的更改通常保持二进制兼容性,但可能通过为已启用可空性的消费者引入警告而造成源兼容性破坏。当消费者将警告视为错误时,这些警告通常会变成构建失败。
TT?发布前应审查 public 可空性更改,尤其是:
- 将输出从 弱化为
T,或添加T?;[MaybeNull] - 将输入从 收紧为
T?,或添加T;[DisallowNull] - 更改泛型约束,例如 、
class?或class;notnull - 更改虚拟成员、接口、委托及其实现上的注解或属性,因为不匹配会产生编译器警告。
为以前属于 nullable-oblivious 的 API 添加注解,也可能产生相同的源兼容性问题。将添加注解后的 API 表面与上一个发布版本进行比较,测试已启用可空性的消费者,并按照库的兼容性策略记录或版本化有意造成的源兼容性破坏。
The field
Keyword (C# 14 / .NET 10)
fieldfield
关键字(C# 14 / .NET 10)
fieldThe contextual keyword lets you write a property accessor body without declaring an explicit backing field. This is a primary NRT scenario (lazily-initialized properties) and the compiler performs a special null-resilience analysis so you do not get nuisance in constructors:
fieldCS8618csharp
public class C
{
public C() { } // No warning: the getter is null-resilient.
string Prop => field ??= GetPropValue();
}See nullable-attributes-reference.md for the full nullability rules (null-resilient vs non-resilient getters, the escape hatch, and setter/constructor analysis).
field[field: AllowNull, MaybeNull]fieldCS8618csharp
public class C
{
public C() { } // No warning: the getter is null-resilient.
string Prop => field ??= GetPropValue();
}有关完整的 可空性规则(null-resilient 与 non-resilient getter、 escape hatch,以及 setter/构造函数分析),请参阅 nullable-attributes-reference.md。
field[field: AllowNull, MaybeNull]Reference Files
参考文件
- nullable-attributes-reference.md: The complete attribute catalog — preconditions (
System.Diagnostics.CodeAnalysis,AllowNull), postconditions (DisallowNull,MaybeNull), conditional postconditions (NotNull,NotNullWhen,MaybeNullWhen), helper methods (NotNullIfNotNull,MemberNotNull), unreachable-code helpers (MemberNotNullWhen,DoesNotReturn), and theDoesNotReturnIfkeyword nullability rules. Each with intent, pattern, and agent rules.field - nrt-migration-playbook-reference.md: Incremental adoption strategy, directive reference, legacy/unannotated API interop, polyfilling nullable attributes for older target frameworks (with tradeoffs and a confirm-before-adding decision process), known static-analysis limitations (arrays of non-nullable references,
#nullablewith reference fields), warning handling, and the full generation checklist.default(struct)
- nullable-attributes-reference.md:完整的 属性目录——前置条件(
System.Diagnostics.CodeAnalysis、AllowNull)、后置条件(DisallowNull、MaybeNull)、条件后置条件(NotNull、NotNullWhen、MaybeNullWhen)、辅助方法(NotNullIfNotNull、MemberNotNull)、不可达代码辅助方法(MemberNotNullWhen、DoesNotReturn),以及DoesNotReturnIf关键字的可空性规则。每个条目均包含用途、模式和 agent 规则。field - nrt-migration-playbook-reference.md:增量采用策略、指令参考、旧版/未注解 API 互操作、为旧目标框架 polyfill 可空性属性(涉及权衡和添加前确认决策流程)、已知静态分析限制(非 null 引用数组、包含引用字段的
#nullable)、警告处理,以及完整的生成检查清单。default(struct)
Generation Checklist (Summary)
生成检查清单(摘要)
- Project — present; disable only around unavoidable legacy code.
<Nullable>enable</Nullable> - Types — non-nullable for required params/returns/properties; only when
T?is valid and expected.null - Initialization — constructors, + object initializers,
required-backed lazy getters, orfieldhelpers. Avoid[MemberNotNull]except as a documented escape hatch.null! - Null checks — explicit guards at public boundaries; narrow with control flow, and extract a non-nullable helper only when it improves complex code; only with a clear invariant.
! - Attributes — apply to express contracts the type system cannot express (see reference file).
- Interop — trust BCL/annotated libraries; add your own guards and attributes when wrapping unannotated APIs.
- Warnings — never ignore; fix design or add attributes rather than suppressing with or
!.#pragma - Compatibility — for released libraries, review public nullability changes as potential source breaks and test nullable-enabled consumers.
- 项目——存在 ;仅在不可避免的旧代码周围禁用。
<Nullable>enable</Nullable> - 类型——必需参数/返回值/属性使用不可空类型;仅当 有效且符合预期时使用
null。T? - 初始化——使用构造函数、+ 对象初始化器、基于
required的惰性 getter,或field辅助方法。除非作为有文档说明的 escape hatch,否则避免使用[MemberNotNull]。null! - Null 检查——在 public 边界设置显式 guard;通过控制流收窄可空性,仅当有助于简化复杂代码时才提取不可空 helper;仅在具有明确不变量时使用 。
! - 属性——应用属性来表达类型系统无法表达的契约(参阅参考文件)。
- 互操作——信任 BCL/已添加注解的库;包装未添加注解的 API 时,添加自己的 guard 和属性。
- 警告——绝不忽略警告;应修复设计或添加属性,而不是使用 或
!进行抑制。#pragma - 兼容性——对于已发布的库,应将 public 可空性更改视为潜在的源兼容性破坏进行审查,并测试已启用可空性的消费者。
References
参考资料
- Nullable reference types (overview)
- Attributes for null-state static analysis
- Nullable migration strategies
- Breaking change: Nullable reference type annotation changes
- Tutorial: Express your design intent with nullable and non-nullable reference types
- What's new in C# 14 (extension members, keyword GA, null-conditional assignment)
field - The contextual keyword (feature spec)
field
- 可空引用类型(概述)
- 用于空状态静态分析的属性
- 可空性迁移策略
- 破坏性变更:可空引用类型注解变更
- 教程:表达你对可空和不可空引用类型的设计意图
- C# 14 新特性(扩展成员、关键字正式发布、null-conditional assignment)
field - 上下文关键字(功能规范)
field