C# 12 与 13 核心新特性与实践指南
深度解析 C# 12 与 13 中最实用的核心特性,包括类和结构体的主构造函数、极简集合表达式、类型别名以及最新的 params 集合与 Lock 对象。
随着 .NET 8 和 .NET 9 的发布,C# 迎来了一系列旨在简化代码结构、提高表达力并消除样板代码的重大更新。本文将梳理 C# 12 与 C# 13 中最值得在日常开发中落地使用的核心新特性。
1. 主构造函数 (Primary Constructors)
在 C# 12 之前,主构造函数只属于 record。现在,C# 12 将这一特性推广到了普通的类(class)和结构体(struct)中。
语法对比
以前我们定义一个带依赖注入的 Service 需要这样写:
public class OrderService
{
private readonly IOrderRepository _repository;
private readonly ILogger<OrderService> _logger;
public OrderService(IOrderRepository repository, ILogger<OrderService> logger)
{
_repository = repository;
_logger = logger;
}
}
在 C# 12 中,可以使用主构造函数将其简化为一行:
public class OrderService(IOrderRepository repository, ILogger<OrderService> logger)
{
public async Task CreateOrderAsync(Order order)
{
logger.LogInformation("Creating order...");
await repository.SaveAsync(order);
}
}
关键点
- 主构造函数的参数作用域是整个类。它们不需要显式地赋给私有字段,编译器会自动在后台生成所需的私有存储。
- 主构造函数参数不是只读字段。如果你需要它们只读,需要小心不要在类的方法中给它们赋新值,或者手动将它们存入
readonly字段。
2. 集合表达式 (Collection Expressions)
C# 12 引入了统一的集合表达式,彻底结束了各种集合类型(数组、List<T>、Span<T> 等)初始化语法各异的混乱状态。
统一的语法 [...]
现在,你可以用方括号语法初始化任何常见的集合类型:
// 数组
int[] array = [1, 2, 3, 4];
// 列表
List<string> list = ["apple", "banana", "cherry"];
// Span
ReadOnlySpan<char> span = ['H', 'e', 'l', 'l', 'o'];
// 空集合
int[] empty = [];
传播运算符 (Spread Operator)
类似于 JavaScript,C# 12 引入了 .. 传播运算符,用于将一个集合的内容插入到另一个集合中:
int[] first = [1, 2];
int[] second = [5, 6];
// 合并为 [1, 2, 3, 4, 5, 6]
int[] combined = [.. first, 3, 4, .. second];
编译优化:编译器会在后台自动优化集合表达式的分配。例如,如果目标是 ReadOnlySpan<T>,编译器会直接将其映射 to 只读数据段,实现零内存分配。
3. 任何类型的 using 别名 (Alias any type)
在 C# 12 中,你现在可以使用 using 关键字为几乎任何类型定义别名,包括元组、数组、指针等,这在处理复杂泛型或多维元组时非常有用。
using Point2D = (int X, int Y);
using GradeList = System.Collections.Generic.List<double>;
public class GeoService
{
public double CalculateDistance(Point2D p1, Point2D p2)
{
return Math.Sqrt(Math.Pow(p1.X - p2.X, 2) + Math.Pow(p1.Y - p2.Y, 2));
}
}
这能够极大提升涉及复杂数学计算或临时数据传输对象(DTO)时代码的可读性,且不需要声明多余的 struct 或 class。
4. C# 13 新增的 params 集合
在 C# 13 之前,params 关键字仅限于数组类型参数:params T[]。这会导致隐式的数组分配成本。
C# 13 将 params 扩展到了任何支持集合表达式的类型,包括 ReadOnlySpan<T>、List<T> 和 IEnumerable<T>:
// C# 13 推荐写法:无隐式堆内存分配
public void LogMessages(params ReadOnlySpan<string> messages)
{
foreach (var msg in messages)
{
Console.WriteLine(msg);
}
}
// 调用时非常自然,且零分配:
LogMessages("Error 1", "Error 2", "Warning");
通过改用 ReadOnlySpan<T>,编译器无需在调用处隐式创建数组对象,避免了垃圾回收(GC)开销,这是追求高性能系统的巨大福音。
5. C# 13 的新型 Lock 对象
在传统的 C# 多线程开发中,我们通常锁住一个 object 实例:
private readonly object _syncRoot = new();
public void DoWork()
{
lock (_syncRoot)
{
// 临界区代码
}
}
C# 13 引入了专门的 System.Threading.Lock 类型。当编译器识别到你锁住的是 Lock 类型的对象时,它会生成更加高效、底层且安全的同步原语,而不是依赖普通的监视器锁(Monitor),这提供了更好的性能和更明确的语义。
// C# 13
private readonly System.Threading.Lock _syncLock = new();
public void DoWork()
{
lock (_syncLock)
{
// 编译器会使用更优化的内部同步逻辑
}
}
小结
C# 12 和 13 不仅在语法层面让我们的日常代码更加整洁(如主构造函数和集合表达式),更在底层和编译器优化层面(如 params ReadOnlySpan 和新的 Lock)为我们编写零分配、高并发的现代 .NET 程序提供了强大保障。
建议在升级到 .NET 8/9 后,积极在团队和项目内推广这些新特性,从而写出更具表达力、运行更快的 C# 代码。