C# 14 已随 .NET 10 正式发布(GA)。本次更新在保持语言类型安全的前提下,大幅减少了样板代码。笔者在梳理官方发布日志后,总结出以下几个最核心的新特性:field 关键字让属性定义更清爽,扩展成员(Extension members)突破了过去仅能扩展方法的限制,同时针对 Span<T> 隐式转换、空条件(?.)赋值操作等也给出了更直观的语法支持。
下文将详细展开这些特性的使用场景、官方代码示例以及升级过程中的注意事项。
1. 扩展成员(Extension members)
C# 14 引入了全新的 extension 块语法。开发者现在不仅可以声明传统的扩展方法,还可以直接声明扩展属性。更进一步,扩展成员现在可以作为被扩展类型的静态成员出现,甚至支持声明静态的、用户定义的运算符。
这一特性解决了以往扩展方法零散、且无法提供属性级别扩展的问题,让扩展库的 API 设计更加内聚和现代化。
官方示例代码:
public static class Enumerable
{
// 扩展块
extension<TSource>(IEnumerable<TSource> source) // IEnumerable<TSource> 的扩展成员
{
// 扩展属性:
public bool IsEmpty => !source.Any();
// 扩展方法:
public IEnumerable<TSource> Where(Func<TSource, bool> predicate) { /*...*/ }
}
// 仅带接收器类型的扩展块
extension<TSource>(IEnumerable<TSource>) // IEnumerable<TSource> 的静态扩展成员
{
// 静态扩展方法:
public static IEnumerable<TSource> Combine(IEnumerable<TSource> first, IEnumerable<TSource> second) { /*...*/ }
// 静态扩展属性:
public static IEnumerable<TSource> Identity => Enumerable.Empty<TSource>();
// 静态用户定义运算符:
public static IEnumerable<TSource> operator + (IEnumerable<TSource> left, IEnumerable<TSource> right) => left.Concat(right);
}
}
在调用时,第一个扩展块中的成员就像 IEnumerable<TSource> 的实例成员一样调用(例如 sequence.IsEmpty);第二个扩展块中的成员则如同静态成员一样调用(例如 IEnumerable<int>.Identity)。
2. field 后端属性(field backed properties)
在以往版本中,如果我们需要控制属性的赋值(例如进行 null 非空校验)或者实现延迟初始化,我们必须手动声明一个后端的私有字段,并编写完整的 get 和 set 访问器。C# 14 引入了 field 关键字(确切来说是 field token),编译器会自动为你合成一个后端字段。
这一特性使得开发者能在不声明显式字段的情况下,编写带有自定义逻辑的属性访问器主体,大大压缩了代码行数。
官方示例代码:
public string Message
{
get;
set => field = value ?? throw new ArgumentNullException(nameof(value));
}
除了参数校验,field 还可用于简化延迟初始化和属性变更通知等常见模式:
// 延迟初始化
public SqlConnection Connection
{
get => field ??= new SqlConnection("connection-string");
}
// 属性变更通知
public string DisplayName
{
get;
set
{
if (field == value) return;
field = value;
OnPropertyChanged();
}
}
// 格式化字符串
public string Email
{
get;
set => field = value?.Trim()?.ToLowerInvariant() ?? string.Empty;
}
3. 空条件赋值(Null-conditional assignment)
C# 14 允许在赋值或复合赋值的左侧,直接使用空条件成员访问运算符(?. 和 ?[])。如果左侧评估为 null,右侧的表达式将根本不会被求值(短路求值)。这直接消灭了代码里大量的 if (customer is not null) 检查代码块。
官方示例代码:
在 C# 14 之前,你必须这样写:
if (customer is not null)
{
customer.Order = GetCurrentOrder();
}
在 C# 14 中,可直接简写为:
customer?.Order = GetCurrentOrder();
需要注意的是:除了常规赋值,复合赋值运算符(如 +=、-= 等)也支持这一特性,但自增和自减(++ 和 --)是不允许使用的。
4. 隐式 Span 转换(Implicit span conversions)
C# 14 在语言层面对 System.Span<T> 和 System.ReadOnlySpan<T> 提供了头等(first-class)的内建支持。新加入的隐式转换允许 ReadOnlySpan<T>、Span<T> 和 T[] 之间进行更自然的隐式类型转换。
由于 Span<T> 系列类型在高性能运行时的各个角落被广泛使用,这些隐式转换在不牺牲内存安全性、不引入额外运行时开销的前提下,极大地改善了高性能 API 的代码编写体验和泛型类型推断。
5. 带修饰符的简单 Lambda 参数(Modifiers on simple lambda parameters)
现在,你可以在不显式指定参数类型的情况下,向 Lambda 表达式的参数添加 scoped、ref、in、out 或 ref readonly 等修饰符。
在以前版本中,只要想添加任何修饰符,就强制要求写出完整的方法参数类型签名,而现在编译器变得更聪明了。
官方示例代码:
delegate bool TryParse<T>(string text, out T result);
// ...
TryParse<int> parse1 = (text, out result) => Int32.TryParse(text, out result);
(注意:params 修饰符依然需要显式指定类型的参数列表)
6. nameof 支持未绑定的泛型类型(Unbound generic types and nameof)
从 C# 14 开始,nameof 的参数可以是未绑定的泛型类型。例如,nameof(List<>) 的求值结果会是 List。而在早期版本的 C# 中,只有封闭泛型类型(如 nameof(List<int>))才能被用来获取 List 的名称。
升级建议
下面是笔者总结的针对此次更新的实战升级指南:
| 考量维度 | 说明 |
|---|---|
| 谁该升 | 正在使用现代 .NET 进行后端、桌面或全栈开发的团队;期望通过新语法重构现有样板代码(特别是大面积使用属性访问和防空赋值控制流)的开发者。 |
| 何时升 | 可随 2025 年 11 月 11 日发布的 .NET 10 GA 版本同步升级开发环境(推荐配合最新版 Visual Studio 2026 或 .NET 10 SDK)。 |
| 注意事项 (破坏性变更) | field 名称冲突:因为引入了 field 关键字概念,如果你的现有代码类型中已经包含名为 field 的变量或字段符号,阅读和编译时可能造成混淆及潜在的破坏性变更。官方建议通过 @field 或 this.field 来消除关键字与标识符之间的歧义,或者最稳妥的做法是:直接重命名现有的 field 变量。 |
参考来源
注:C# 14 随 .NET 10 GA 版本同步发布,官方发布日期确认记为 2025-11-11。