C# 14中的新field关键字
C# 中的自动属性简洁,但当您需要在 setter 中进行验证或转换逻辑时,您一直不得不完全抛弃它们并编写一个带有手动基础字段的完整属性。 从一行跳到七行是为了添加一个单一保护条款付出的巨大代价。 C# 14引入了field关键字来弥补这一差距,让您能够自定义getter或setter,同时编译器仍然为您管理后备字段。
在他的视频《C# 14 新 field 关键字》中,Tim Corey 演示了这个功能解决的问题,演练了 setter 验证的实际例子,并介绍了升级前您应该了解的命名冲突。 我们将详细跟踪每一步,以便您能够自信地在自己的属性中使用field。
设置:一个简单的 Person 模型
[0:12 - 1:07] Tim从一个运行在.NET 10和Visual Studio 2026上的控制台应用程序开始。演示主要围绕一个具有几个属性的Person类进行:
public required string FirstName { get; set; }
public required string LastName { get; set; }
public int Age { get; set; }public required string FirstName { get; set; }
public required string LastName { get; set; }
public int Age { get; set; }还有一个由私有字段支持的Demo属性,一旦命名冲突浮现,它就变得相关了。 在LastName = "Corey"的实例,然后打印出姓氏、年龄和演示值。 一切如预期输出:"Corey", 0(默认整数)和"test"。
问题:自动属性接受错误数据
[1:23 - 2:49] 当Tim在构造后将LastName时,问题就出现了:
p.LastName = null;p.LastName = null;即使required并被类型化为不可空字符串,赋值仍然能够编译通过。 required修饰符仅在对象初始化期间强制要求提供一个值; 它不能阻止某人之后将属性设置为null。 结果是在运行时没有抛出错误而是留下一个空白的姓氏。
这是数据完整性的一个真正缺口。 类型系统用一个 可空 引用警告您,但这只是编译时提示,而不是运行时保护。 如果您的应用程序依赖于LastName始终包含有效字符串,仅靠自动属性无法强制执行该合同。
旧版修复:带手动基础字段的完整属性
[2:58 - 4:19] 在 C# 14 之前,标准解决方案是将自动属性转换为带有显式 基础字段 的完整属性:
private string _lastName;
public required string LastName
{
get => _lastName;
set => _lastName = value ?? throw new ArgumentNullException(nameof(LastName));
}private string _lastName;
public required string LastName
{
get => _lastName;
set => _lastName = value ?? throw new ArgumentNullException(nameof(LastName));
}Tim 运行此程序并确认异常正确触发:"值不能为空。 参数名称:LastName。" 这种方法有效,但需要声明一个私有字段,连接 getter 和 setter,并在多个行中重复属性名称。 对于一个单一验证规则,这是一种很大的仪式。
此情况下的 getter 并没有什么特别; 它不变地返回字段。 然而,由于语法要求,一旦您离开自动属性领域,您仍然需要显式地编写它。 Tim 将这种繁琐视为新功能背后的动机。
C# 14 解决方案:field 关键字
[4:23 - 5:47] C# 14 引入了一个中间立场。 与其自己声明一个私有后备字段,不如在getter或setter中使用上下文关键字field直接引用编译器生成的后备字段:
public required string LastName
{
get;
set => field = value ?? throw new ArgumentNullException(nameof(LastName));
}public required string LastName
{
get;
set => field = value ?? throw new ArgumentNullException(nameof(LastName));
}getter仍然是一个不需要主体的自动实现的get;。 setter使用value。 编译器在幕后创建并管理基础字段,就像标准自动属性一样。
运行演示在空值赋值时生成相同的ArgumentNullException。 这种行为与手动支持的版本相同,从七行压缩到仅关注需要自定义的部分。 您保留自动属性 getter,仅为 setter 添加逻辑,完全跳过手动字段声明。
这为一个简单的自动属性(一行,无验证)和一个完整属性(七行或更多行,完整控制)之间提供了有用的中间步骤。 当您的逻辑只触及 setter 时,您不再需要为重写 getter 支付语法成本。
使用 Setter Guard 验证年龄
[6:16 - 7:39] 为了证明Age属性添加了范围验证:
public int Age
{
get;
set
{
if (value > 0 && value < 120)
field = value;
}
}public int Age
{
get;
set
{
if (value > 0 && value < 120)
field = value;
}
}在这里,setter 默默忽略不在合理范围内的值。 分配field从未被写入。 Tim注意到您可以抛出异常,但这种安静的方式展示了setter主体仍然可以包含您需要的任何逻辑,同时依赖field存储。
该模式广泛适用:限定数值范围,修剪字符串空格,规范化大小写,或者您希望在每次设置属性时应用的任何转换。
与现有 field 变量的命名冲突
[7:39 - 9:43] Tim 介绍了一个故意的边缘案例。 演示类有一个字面命名为field的私有成员:
private string field = "test";private string field = "test";一旦C# 14生效,编译器在属性存取器中将field视为关键字而非变量。 这意味着引用field的属性会默默地从属性后面的隐藏存储中读取(为空),而不是包含"test"的字符串成员。 输出变为空白,没有编译错误,只有警告。
存在两个变通办法。使用this.field作为前缀告诉编译器您指的是类级成员,而不是关键字。 或者,@field转义方式效果相同:
// Both refer to the instance variable, not the keyword
string demo => this.field;
string demo => @field;// Both refer to the instance variable, not the keyword
string demo => this.field;
string demo => @field;Tim强烈建议在升级到C# 14时重命名任何名为field的变量。在您的IDE中快速"全部重命名"可以永久消除歧义。 冲突仅在属性访问器内出现; 构造函数和方法将field解析为预期的变量名,因为这些上下文中没有隐式后备存储。
总结:减少模板代码,保持相同的控制
[10:04 - 10:28] field关键字填补了日常C#代码中的实际空白。 需要一个保护条款或转换的属性不再需要通过手动设置字段进行全面重写。 您只定制需要逻辑的访问器并将其他部分保留为标准自动实现。
结论
[10:28 - 10:35] 总结:C# 14的field关键字让您可以直接访问任何属性存取器中的隐式后备存储。 在不放弃不需要自定义的部分的自动属性语法的情况下,使用它添加 setter 验证、getter 转换或两者。
在升级之前,搜索您的代码库中的任何名为field的变量并重命名。 这一预防措施避免了该功能引入的唯一真正陷阱。 除此之外,这是一种自然适合到大多数开发人员已经设置模型的途径的模板代码的干净减少。
示例提示:如果您只需要验证setter,可以将getter保留为无主体的简单get;。 编译器将其处理为自动属性 getter,避免编写不添加任何内容的透传返回语句。
观看他的 YouTube 视频以获取关于 C# 语言特性的更多见解。

