C# 12/13 新特性
大约 10 分钟约 2896 字
C# 12/13 新特性
简介
C# 语言持续演进,C# 12 和 C# 13 引入了多项实用的新特性,旨在简化代码编写、提升性能和增强开发体验。C# 12 带来了主构造函数、集合表达式、内联数组等重要特性;C# 13 则进一步增强了 ref struct、引入了扩展类型(Extension Types)和更灵活的 params 集合等。这些新特性让 C# 代码更加简洁、高效且富有表现力。
特点
C# 12 新特性
主构造函数(Primary Constructors)
主构造函数允许在类和结构体声明中直接定义构造函数参数,无需显式编写构造函数体。
// C# 12 之前 - 需要显式定义构造函数和字段
public class ProductService
{
private readonly IProductRepository _repository;
private readonly ILogger<ProductService> _logger;
public ProductService(IProductRepository repository, ILogger<ProductService> logger)
{
_repository = repository;
_logger = logger;
}
public async Task<Product> GetAsync(int id)
{
_logger.LogInformation("获取产品: {Id}", id);
return await _repository.GetByIdAsync(id);
}
}
// C# 12 - 主构造函数,参数可直接在类成员中使用
public class ProductService(
IProductRepository repository,
ILogger<ProductService> logger)
{
public async Task<Product> GetAsync(int id)
{
logger.LogInformation("获取产品: {Id}", id);
return await repository.GetByIdAsync(id);
}
public async Task<IEnumerable<Product>> GetAllAsync()
{
logger.LogInformation("获取所有产品");
return await repository.GetAllAsync();
}
}
// 主构造函数与 DI 完美结合
public class OrderController(
IOrderService orderService,
ILogger<OrderController> logger) : ControllerBase
{
[HttpGet("{id}")]
public async Task<ActionResult<Order>> Get(int id)
{
logger.LogInformation("查询订单: {Id}", id);
var order = await orderService.GetByIdAsync(id);
return order is not null ? Ok(order) : NotFound();
}
}
// 主构造函数也可用于 struct
public readonly struct Point(double x, double y)
{
public double X { get; } = x;
public double Y { get; } = y;
public double Distance => Math.Sqrt(X * X + Y * Y);
public override string ToString() => $"({X}, {Y}),距离原点: {Distance:F2}";
}
// 主构造函数中参数的可变性
public class Configuration(string name, int version)
{
// 可变的属性可以基于主构造函数参数
public string Name { get; set; } = name;
public int Version { get; set; } = version;
// 如果需要不可变,可以使用 init
public string Environment { get; init; } = "Production";
}集合表达式(Collection Expressions)
集合表达式提供了一种统一的语法来初始化各种集合类型。
// 统一的集合初始化语法
// C# 12 之前
int[] array = new int[] { 1, 2, 3 };
List<string> list = new List<string> { "a", "b", "c" };
Span<int> span = new int[] { 1, 2, 3 };
HashSet<string> set = new HashSet<string> { "a", "b" };
// C# 12 集合表达式 - 使用方括号
int[] array = [1, 2, 3];
List<string> list = ["a", "b", "c"];
Span<int> span = [1, 2, 3];
HashSet<string> set = ["a", "b"];
ImmutableArray<int> immutable = [1, 2, 3];
// 展开运算符(..)- 合并集合
int[] a = [1, 2, 3];
int[] b = [4, 5, 6];
int[] combined =[.. a, .. b]; // [1, 2, 3, 4, 5, 6]
// 在展开中插入元素
int[] withExtra = [0, .. a, 100, .. b]; // [0, 1, 2, 3, 100, 4, 5, 6]
// 空集合
int[] empty = [];
// 实际应用 - 构建配置列表
public class MiddlewareConfig
{
public List<string> GetMiddlewareChain(bool enableAuth, bool enableLogging)
{
var baseChain = new[] { "ExceptionHandler", "Routing" };
var authChain = enableAuth ? new[] { "Authentication", "Authorization" } : Array.Empty<string>();
var loggingChain = enableLogging ? new[] { "RequestLogging" } : Array.Empty<string>();
var endpointChain = new[] { "EndpointMapper" };
return[.. baseChain, .. authChain, .. loggingChain, .. endpointChain];
}
}
// 集合表达式与 LINQ 结合
public int[] ProcessNumbers(int[] input)
{
var positives = input.Where(x => x > 0).ToArray();
var negatives = input.Where(x => x < 0).ToArray();
var zeros = input.Where(x => x == 0).ToArray();
return[.. positives, .. zeros, .. negatives];
}内联数组(Inline Arrays)
// 内联数组 - 在结构体中直接嵌入固定大小的数组,减少堆分配
[System.Runtime.CompilerServices.InlineArray(4)]
public struct FourInts
{
private int _element0;
}
// 使用
var buffer = new FourInts();
for (int i = 0; i < 4; i++)
buffer[i] = i * 10;
// 作为 Span 使用
Span<int> span = buffer;
Console.WriteLine(string.Join(", ", span.ToArray())); // 0, 10, 20, 30
// 实际应用 - 高性能缓冲区
[System.Runtime.CompilerServices.InlineArray(8)]
public struct SmallByteBuffer
{
private byte _element0;
}
public void ProcessData()
{
var buffer = new SmallByteBuffer();
Span<byte> bytes = buffer;
bytes[0] = 0x89;
bytes[1] = 0x50;
// ... 不需要从堆上分配 byte[]
}别名任意类型(Alias Any Type)
// C# 12 支持对任意类型使用 using 别名
using Point = (int X, int Y);
using Matrix = int[,];
using Handler = Func<string, CancellationToken, Task<bool>>;
using StringMap = System.Collections.Generic.Dictionary<string, string>;
// 使用别名
Point origin = (0, 0);
Point target = (100, 200);
Matrix identity = new int[3, 3]
{
{ 1, 0, 0 },
{ 0, 1, 0 },
{ 0, 0, 1 }
};
Handler handler = async (input, ct) =>
{
Console.WriteLine($"处理: {input}");
await Task.Delay(100, ct);
return true;
};
StringMap config = new()
{
["Host"] = "localhost",
["Port"] = "8080"
};
// 元组别名在方法签名中使用
public double CalculateDistance(Point a, Point b)
{
var dx = b.X - a.X;
var dy = b.Y - a.Y;
return Math.Sqrt(dx * dx + dy * dy);
}实验性特性:拦截器(Interceptors)
// 拦截器允许在编译时将方法调用替换为其他实现
// 需要在项目文件中启用:
// <PropertyGroup>
// <InterceptorsPreviewNamespaces>true</InterceptorsPreviewNamespaces>
// </PropertyGroup>
// 原始方法
public static class Logger
{
public static void Log(string message) => Console.WriteLine(message);
}
// 拦截器 - 替换特定位置的调用
[System.Runtime.CompilerServices.InterceptsLocation("Program.cs", 10)]
public static void InterceptedLog(string message)
{
Console.WriteLine($"[拦截] {DateTime.UtcNow:O} - {message}");
}C# 13 新特性
ref struct 改进
// C# 13 允许 ref struct 实现接口和使用泛型约束
public interface IBuffer
{
int Length { get; }
void Clear();
}
// ref struct 可以实现接口
public ref struct UnsafeBuffer : IBuffer
{
private Span<byte> _data;
public UnsafeBuffer(Span<byte> data) => _data = data;
public int Length => _data.Length;
public void Clear() => _data.Clear();
public void Write(ReadOnlySpan<byte> source)
{
if (source.Length > _data.Length)
throw new ArgumentException("缓冲区不足");
source.CopyTo(_data);
}
public ReadOnlySpan<byte> Read(int offset, int count)
{
return _data.Slice(offset, count);
}
}
// ref struct 可以作为泛型参数(在 allows ref struct 约束下)
public class BufferProcessor<T> where T : allows ref struct, IBuffer
{
public void Process(T buffer)
{
Console.WriteLine($"处理缓冲区,长度: {buffer.Length}");
buffer.Clear();
}
}
// 使用
Span<byte> memory = stackalloc byte[256];
var buffer = new UnsafeBuffer(memory);
var processor = new BufferProcessor<UnsafeBuffer>();
processor.Process(buffer);
// ref struct 中的 ref 字段
public ref struct SpanWrapper<T>
{
public ref T Value; // ref 字段
public SpanWrapper(ref T value) => Value = ref value;
public void Update(T newValue) => Value = newValue;
}params 集合增强
// C# 13 允许 params 使用 ReadOnlySpan<T> 和其他集合类型
public void Process(params ReadOnlySpan<int> values)
{
Console.WriteLine($"收到 {values.Length} 个值");
foreach (var v in values)
Console.WriteLine($" - {v}");
}
// 调用 - 不产生堆分配
Process(1, 2, 3, 4, 5);
// params 配合自定义集合
public void Configure(params List<string> settings)
{
foreach (var setting in settings)
Console.WriteLine($"配置: {setting}");
}
// 高性能日志:params ReadOnlySpan
public static class Logger
{
// 使用 params ReadOnlySpan 避免数组分配
public static void Info(params ReadOnlySpan<string> parts)
{
var message = string.Join(" ", parts);
Console.WriteLine($"[INFO] {DateTime.UtcNow:HH:mm:ss} {message}");
}
}
// 使用
Logger.Info("订单", orderId.ToString(), "已创建", "金额:", amount.ToString("C"));部分属性(Partial Properties)
// C# 13 支持部分属性,常用于源代码生成器
public partial class ViewModel
{
// 声明部分属性
public partial string DisplayName { get; set; }
public partial bool IsValid { get; }
}
// 实现(通常由源代码生成器自动生成)
public partial class ViewModel
{
private string _displayName = "";
public partial string DisplayName
{
get => _displayName;
set => _displayName = value?.Trim() ?? "";
}
public partial bool IsValid => !string.IsNullOrEmpty(_displayName);
}
// 配合源代码生成器使用(如 CommunityToolkit.Mvvm)
public partial class UserViewModel : ObservableObject
{
[ObservableProperty]
private string _name = "";
[ObservableProperty]
private int _age;
[RelayCommand]
private void Save()
{
Console.WriteLine($"保存用户: {Name}, 年龄: {Age}");
}
}锁对象(Lock Object)
// C# 13 引入 System.Threading.Lock 对象,替代 lock(obj) 模式
// 新的 Lock 类型比 Monitor.Enter/Exit 有更好的性能
// 旧方式
public class OldStyleCounter
{
private readonly object _lock = new();
private int _count;
public void Increment()
{
lock (_lock)
{
_count++;
}
}
}
// C# 13 新方式 - 使用 System.Threading.Lock
public class NewStyleCounter
{
private readonly Lock _lock = new();
private int _count;
public void Increment()
{
lock (_lock)
{
_count++;
}
}
// Lock 还支持 Scope 模式
public void IncrementWithScope()
{
using (_lock.EnterScope())
{
_count++;
} // 自动释放锁
}
public int GetCount()
{
lock (_lock) return _count;
}
}扩展类型(Extension Types)- C# 13 预览
// 扩展类型是对扩展方法的全面增强
// 注意:此特性在 C# 13 中为预览功能
// 传统的扩展方法
public static class StringExtensions
{
public static bool IsNullOrEmpty(this string? str) =>
string.IsNullOrEmpty(str);
public static string Truncate(this string str, int maxLength) =>
str.Length <= maxLength ? str : str[..maxLength];
}
// 扩展类型(预览特性)- 可以定义一组扩展作为类型
// extension StringExtensions for string
// {
// bool IsNullOrEmpty => string.IsNullOrEmpty(this);
// string Truncate(int maxLength) =>
// Length <= maxLength ? this : this[..maxLength];
// }
// 使用扩展方法
string text = "Hello World!";
var truncated = text.Truncate(5); // "Hello"
var empty = "".IsNullOrEmpty(); // true优点
缺点
总结
C# 12 和 13 的主要目标是简化代码、提升性能和增强语言表现力。主构造函数和集合表达式是最实用的新特性,可以立即在项目中采用并显著减少样板代码。ref struct 改进和 params ReadOnlySpan 则为高性能场景提供了更优雅的解决方案。建议团队逐步引入这些新特性,在代码审查中统一使用规范,以保持代码风格的一致性。
关键知识点
- 先明确这个主题影响的是语法层、运行时层,还是性能与可维护性层。
- 学习时要同时关注语言表面写法和编译器、JIT、GC 等底层行为。
- 真正有价值的是知道“为什么这样写”和“在什么边界下不能这样写”。
项目落地视角
- 把示例改成最小可运行样例,并观察编译输出、运行结果和异常行为。
- 如果它会进入团队代码规范,最好同步补充命名约定、禁用场景和替代方案。
- 涉及性能结论时,优先用 Benchmark 或实际热点链路验证,而不是凭感觉判断。
常见误区
- 只记语法糖,不知道底层成本。
- 把适用于小样例的写法直接搬到高并发或大对象场景里。
- 忽略框架版本、语言版本和运行时差异,导致结论失真。
进阶路线
- 继续向源码、IL、JIT 行为和 BCL 实现层深入。
- 把知识点和代码评审、性能诊断、面试复盘结合起来。
- 把同类主题做横向对比,例如值类型与引用类型、迭代器与 async 状态机、反射与 Source Generator。
适用场景
- 当你准备把《C# 12/13 新特性》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在需要理解语言特性、运行时行为或 API 边界时阅读。
- 当代码开始出现性能瓶颈、可维护性问题或语义歧义时,这类主题会直接影响实现质量。
落地建议
- 先写最小可运行样例,再把结论迁移到真实业务代码。
- 同时记录这个特性的收益、限制和替代方案,避免为了“高级”而使用。
- 涉及内存、并发或序列化时,最好配合调试器或基准测试验证。
排错清单
- 先确认问题属于编译期、运行期还是语义误用。
- 检查是否存在隐式转换、装箱拆箱、闭包捕获或上下文切换等隐藏成本。
- 查看异常栈、日志和最小复现代码,优先排除使用姿势问题。
复盘问题
- 如果把《C# 12/13 新特性》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《C# 12/13 新特性》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《C# 12/13 新特性》最大的收益和代价分别是什么?
