C# 14 实用新特性速览:扩展成员与 field 关键字
.NET 10 是 LTS 版本,随它一同发布的 C# 14 已经稳定落地。新特性不少,但如果只挑两个「日常项目里马上能用上」的,我投扩展成员(extension members)和field 关键字各一票。这篇文章结合我们项目里的真实代码,聊聊它们适合什么场景。
一、扩展成员:给类型「加属性」终于名正言顺
过去的扩展方法有个尴尬之处:只能模拟「方法」,不能模拟「属性」和「静态成员」。于是历史上到处都是 GetWordCount() 这种明明是属性语义、却只能写成方法的名字。C# 14 的 extension 块解决了这个问题:
public static class StringExtensions
{
extension(string source)
{
// 扩展属性:调用方看到的就是属性
public int WordCount =>
source.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length;
// 也可以定义扩展方法
public bool HasDuplicateWords()
{
var words = source.Split(' ', StringSplitOptions.RemoveEmptyEntries);
return words.Length != words.Distinct().Count();
}
}
}
// 调用方式与普通实例成员完全一致
int count = "hello vibe coding world".WordCount;
几个实践建议:
- 扩展属性适合纯计算、无副作用的语义(
WordCount、IsEmpty、DaysUntil); - 扩展成员不能访问私有状态,所以它天生是「从外部观察类型」的视角,不要试图用它绕过封装;
- 旧的扩展方法可以渐进迁移,新旧写法在调用侧完全兼容。
二、field 关键字:告别手写的后备字段
自动属性很香,但只要想在 getter/setter 里加一点逻辑,就得自己声明一个后备字段,样板代码立刻多了四行。C# 14 用 field 上下文关键字直连编译器生成的后备字段:
public class Product
{
public required string Name
{
get;
set => field = value?.Trim() ?? throw new ArgumentNullException(nameof(value));
}
public int Stock
{
get => field;
set => field = value >= 0
? value
: throw new ArgumentOutOfRangeException(nameof(value));
}
}
注意两点:一是 field 只在确实没有同名成员时才被当作关键字,老代码里叫 field 的标识符不会被破坏;二是它只是省掉了后备字段的声明,语义和过去手写完全一致,可以放心重构成这个写法。
三、顺手提一下 null 条件赋值
小特性,但使用频率极高——?. 现在可以直接出现在赋值号左边:
// C# 13 及之前
if (order is not null)
{
order.Status = OrderStatus.Paid;
}
// C# 14
order?.Status = OrderStatus.Paid;
// 甚至可以级联
customer?.Profile?.LastLoginAt = DateTime.UtcNow;
四、升级建议
把 LangVersion 提到 14 之后,不建议一口气把全仓库旧代码都改写成新语法。我的做法是:新代码一律使用新特性,旧代码只在恰好要改动的文件里顺手迁移,用差异化提交保持 review 成本可控。语言特性是工具,不是 KPI。
此外,C# 14 还带了 partial 构造函数与事件、lambda 参数修饰符、nameof 支持未绑定泛型等改进,篇幅所限不展开,感兴趣的可以直接翻官方 What's new 文档。
一句话总结:扩展成员让你更体面地「扩展」类型,field 关键字让你更体面地「守住」属性。两个都是低风险、高回报的特性,值得尽快用起来。