【.NET新特性·第2篇】C# 12 全特性回顾:语法糖的盛宴
C# 12 带来了主构造函数、集合表达式、Inline Arrays 等 8 个新特性让代码更简洁版本定位适用版本.NET 8 | C# 12 前置知识C# 11 基础语法背景C# 11 引入了原始字符串字面量、list patterns 等特性但开发者们期待更多语法糖来简化日常编码。C# 12 响应了这些需求带来了 8 个新特性其中主构造函数和集合表达式是最受关注的。C# 12 新特性一览特性简述实用性主构造函数类和结构体可以在声明时定义构造函数参数⭐⭐⭐⭐⭐集合表达式用[1, 2, 3]语法初始化集合包括字典⭐⭐⭐⭐⭐Inline Arrays在结构体中内联数组提升性能⭐⭐⭐可选 Lambda 参数Lambda 表达式支持默认参数⭐⭐⭐ref readonly 参数方法参数标记为只读引用零拷贝传递⭐⭐⭐Alias any type为任意类型创建别名⭐⭐⭐⭐Experimental 属性标记实验性 API⭐⭐Interceptors拦截方法调用预览功能⭐⭐特性详解1. 主构造函数Primary Constructors之前的做法需要显式定义构造函数// C# 11 及之前 public class UserService { private readonly IUserRepository _repository; private readonly ILoggerUserService _logger; public UserService(IUserRepository repository, ILoggerUserService logger) { _repository repository; _logger logger; } public async TaskUser GetUserAsync(int id) { _logger.LogInformation(Getting user {Id}, id); return await _repository.GetByIdAsync(id); } }C# 12 的做法主构造函数直接在类声明中定义// C# 12 主构造函数 public class UserService(IUserRepository repository, ILoggerUserService logger) { public async TaskUser GetUserAsync(int id) { logger.LogInformation(Getting user {Id}, id); return await repository.GetByIdAsync(id); } }实际使用场景// 场景 1依赖注入 - 最常见的用法 public class OrderService( IOrderRepository orderRepository, IPaymentService paymentService, ILoggerOrderService logger) { public async TaskOrder CreateOrderAsync(CreateOrderRequest request) { logger.LogInformation(Creating order for {CustomerId}, request.CustomerId); var order new Order { Items request.Items }; await paymentService.ProcessPaymentAsync(order.Total); await orderRepository.AddAsync(order); return order; } } // 场景 2记录类型record- 自动生成属性 public record Product(string Name, decimal Price, int Stock); // 自动生成Name、Price、公共只读属性 // 场景 3不可变 DTO public record CreateOrderRequest( string CustomerId, ListOrderItem Items, string ShippingAddress); // 场景 4工厂模式 - 简化参数传递 public class LoggerFactory(string prefix) { public ILogger CreateLoggerT() { return LoggerFactory.Create(builder builder.AddConsole().SetMinimumLevel(LogLevel.Information)); } }注意事项主构造函数参数在整个类体中可用如果需要存储字段需要显式声明非 record 类型不会自动生成公共属性// 需要存储字段时 public class UserService(IUserRepository repository) { private readonly IUserRepository _repository repository; public async TaskUser GetUserAsync(int id) { return await _repository.GetByIdAsync(id); } }2. 集合表达式Collection Expressions之前的做法使用new关键字和初始化器// C# 11 及之前 Listint numbers new Listint { 1, 2, 3, 4, 5 }; int[] array new int[] { 10, 20, 30 }; Spanint span new int[] { 1, 2, 3 };C# 12 的做法使用[1, 2, 3]语法// C# 12 集合表达式 Listint numbers [1, 2, 3, 4, 5]; int[] array [10, 20, 30]; Spanint span [1, 2, 3];展开运算符使用..合并集合int[] first [1, 2, 3]; int[] second [4, 5, 6]; int[] combined [..first, ..second]; // [1, 2, 3, 4, 5, 6]3. Inline Arrays之前的做法使用固定大小的数组// C# 11 及之前 public struct Buffer { public int[] Data; }C# 12 的做法使用 Inline Array 特性// C# 12 Inline Array [InlineArray(10)] public struct Buffer { private int _element0; } // 使用 Buffer buffer default; buffer[0] 1; buffer[1] 2;优势更好的性能栈分配更安全边界检查与 Span 兼容4. 可选 Lambda 参数之前的做法Lambda 不支持默认参数// C# 11 及之前 Funcint, int, int add (a, b) a b; // 无法设置默认值C# 12 的做法Lambda 参数支持默认值// C# 12 Lambda 默认参数 var func (int x, int y 10) x y; Console.WriteLine(func(5)); // 15 Console.WriteLine(func(5, 20)); // 25更多使用场景// 场景 1日志记录 - 默认日志级别 var log (string message, LogLevel level LogLevel.Information) Logger.Log(level, message); log(User logged in); // 默认 Information log(Cache miss, LogLevel.Warning); // 指定 Warning // 场景 2数据处理 - 默认分隔符 var split (string input, char separator ,) input.Split(separator); var csv a,b,c; var result split(csv); // [a, b, c] var pipe x|y|z; var result2 split(pipe, |); // [x, y, z] // 场景 3重试逻辑 - 默认重试次数 var withRetry (Action action, int maxRetries 3) { for (int i 0; i maxRetries; i) { try { action(); return; } catch { if (i maxRetries - 1) throw; } } }; withRetry(() RiskyOperation()); // 默认重试 3 次 withRetry(() RiskyOperation(), 5); // 重试 5 次5. ref readonly 参数之前的做法使用in关键字传递只读引用// C# 11 及之前 public double CalculateArea(in Rectangle rect) { return rect.Width * rect.Height; }C# 12 的做法使用ref readonly明确表达意图// C# 12 ref readonly public double CalculateArea(ref readonly Rectangle rect) { return rect.Width * rect.Height; } // 调用 var rect new Rectangle(10, 20); CalculateArea(ref rect); // OK CalculateArea(in rect); // 也 OKref readonly 与 in 兼容性能说明参数类型传递方式内存开销适用场景值类型复制整个结构体高大结构体小型结构体ref引用传递低需要修改的参数in/ref readonly只读引用低大型结构体只读访问// 性能对比示例 public struct LargeStruct { public double X, Y, Z; public double[] Data; // 引用类型 } // ❌ 值传递 - 复制整个结构体包括数组引用 public void Process(LargeStruct data) { /* ... */ } // ✅ ref readonly - 只传递引用零拷贝 public void Process(ref readonly LargeStruct data) { /* ... */ } // 实际应用数学库 public static class Matrix4x4 { public static Matrix4x4 Multiply( ref readonly Matrix4x4 a, ref readonly Matrix4x4 b) { // 高效的矩阵乘法避免值类型复制 return new Matrix4x4(); } }6. Alias any type之前的做法只能为命名空间中的类型创建别名// C# 11 及之前 using IntList System.Collections.Generic.Listint;C# 12 的做法可以为任意类型创建别名包括元组// C# 12 using Point (int X, int Y); using PersonRecord (string Name, int Age); Point p (10, 20); PersonRecord person (Alice, 30);7. Experimental 属性// 标记实验性 API [Experimental(This API is experimental and may change in future releases)] public void ExperimentalMethod() { // ... } // 使用时会产生警告 ExperimentalMethod(); // 警告ExperimentalMethod 是实验性的8. Interceptors预览功能Interceptors 允许拦截方法调用主要用于源生成器场景。命名约定与编译时验证规则// 1. 拦截器必须是静态类中的静态方法 public static class Interceptors { // 2. 方法签名必须与目标方法匹配参数类型、返回类型 [InterceptsLocation( path\to\file.cs, // 3. 文件路径相对路径 line: 5, // 4. 行号必须精确匹配 column: 5)] // 5. 列号必须精确匹配 public static void InterceptedMethod(this MyClass obj) { // 拦截原始方法调用 } } // 6. 验证规则示例 public class MyClass { public void MyMethod() { } // 原始方法 } // ✅ 正确方法签名匹配 public static void InterceptedMethod(this MyClass obj) { } // ❌ 错误参数类型不匹配 public static void InterceptedMethod(this MyClass obj, int extra) { } // ❌ 错误不是静态方法 public void InterceptedMethod(this MyClass obj) { } // 7. 命名约定方法名可以是任意有效标识符 [InterceptsLocation(file.cs, 1, 1)] public static void MyInterceptor(this MyClass obj) { } // ✅ 有效 // 8. 文件路径规则 // - 相对于项目根目录 // - 使用 Windows 风格路径分隔符 // - 支持通配符部分场景 [InterceptsLocation(src\MyProject\*.cs, 10, 5)] public static void Interceptor(this MyClass obj) { }实战场景主构造函数适合的场景依赖注入减少样板代码不可变类简化初始化DTO/POCO快速定义数据类集合表达式适合的场景方法返回值return [1, 2, 3];参数传递DoSomething([1, 2, 3]);集合初始化替代new ListTInline Arrays 适合的场景高性能场景避免堆分配与 Span 配合处理切片数据固定大小缓冲区替代 unsafe 代码迁移建议自动迁移大部分 C# 12 特性是向后兼容的可以直接使用# 更新项目文件中的 LangVersion PropertyGroup LangVersion12/LangVersion /PropertyGroup需要注意的 breaking changes主构造函数参数作用域参数在整个类体中可用可能与现有变量名冲突集合表达式类型推断编译器可能推断出不同的类型Inline Array 边界检查运行时会进行边界检查一句话总结C# 12 是语法糖的盛宴主构造函数和集合表达式让你的代码更简洁、更现代。官方文档Whats new in C# 12Primary constructorsCollection expressions