截至2024年12月。以下是C#各版本主要新特性的设计动机和解决的痛点分析,按版本从新到旧组织:
C# 13 (.NET 9) - 最新
1. 增强的 params 集合支持
为什么:传统 params 只允许数组,限制了性能优化空间。 解决痛点:高频调用方法时,数组分配造成GC压力。
// 之前:params只能接受数组,调用时必然分配内存
void Print(params int[] numbers) { }
// C# 13:支持Span/List,调用方可避免堆分配
void Print(params ReadOnlySpan<int> numbers) { }
// 调用:Span直接指向栈上或数组内存,零分配
int[] data = [1,2,3];
Print(data.AsSpan());2. 新型锁 System.Threading.Lock
为什么:传统 lock 在异步场景易死锁,且无法配置超时。 解决痛点:异步编程中需要更灵活的同步原语。
private readonly Lock _lock = new();
async Task UpdateAsync()
{
// 之前:await在lock内会导致死锁风险
// lock(_obj) { await Task.Delay(100); } // ❌ 危险
// C# 13:支持异步获取锁,避免死锁
using (await _lock.AcquireAsync())
{
await Task.Delay(100); // ✅ 安全
}
}C# 12 (.NET 8)
3. 主构造函数(扩展到所有 class/struct)
为什么:record 的主构造函数太香,但普通类还得写样板代码。 解决痛点:减少构造函数和字段声明的重复劳动。
// 之前:普通类需要手动声明字段和构造函数
class Circle
{
private readonly double _radius;
public Circle(double radius) => _radius = radius;
public double Area => Math.PI * _radius * _radius;
}
// C# 12:主构造函数参数自动成为类成员
class Circle(double radius)
{
public double Area => Math.PI * radius * radius; // 参数可在全类使用
}4. 集合表达式
为什么:数组、列表、Span初始化语法不统一,合并集合啰嗦。 解决痛点:提供统一、简洁的集合创建语法。
// 之前:多种语法混用
int[] arr = new[] { 1, 2, 3 };
List<int> list = new List<int> { 1, 2, 3 };
Span<int> span = stackalloc[] { 1, 2, 3 };
// C# 12:统一语法 + 展开运算
int[] arr = [1, 2, 3];
List<int> list = [1, 2, 3];
int[] combined = [..arr, 4, ..list]; // [1,2,3,4,1,2,3]5. 内联数组(Inline Arrays)
为什么:高性能场景需要固定大小数组,但结构体嵌套数组性能差。 解决痛点:提供零开销的固定大小数组。
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
private int _element0; // 编译器展开为10个连续字段
}
Buffer buf = new();
buf[^1] = 42; // 支持索引,无堆分配
// 内存布局:struct内直接包含10个int,无引用跳转C# 11 (.NET 7)
6. 原始字符串字面量(Raw String Literals)
为什么:JSON/SQL等多行字符串需要大量转义,可读性差。 解决痛点:消除转义符,保持原始格式。
// 之前:JSON里的引号需要转义,惨不忍睹
string json = "{\"name\":\"Alice\",\"age\":30}";
// C# 11:使用"""包裹,字符串内容原样输出
string json = """
{
"name": "Alice",
"age": 30
}
""";
// 编译器自动处理缩进和引号,无需转义7. 列表模式匹配
为什么:检查数组/列表的特定结构需要写大量循环和条件。 解决痛点:声明式匹配集合结构。
// 之前:检查数组是否以1,2开头并以10结尾
bool Check(int[] arr)
{
if (arr.Length < 3) return false;
if (arr[0] != 1 || arr[1] != 2) return false;
return arr[^1] == 10;
}
// C# 11:模式匹配
bool Check(int[] arr) => arr is [1, 2, .., 10];
// 还能捕获切片
if (arr is [1, .. var middle, 10])
{
Console.WriteLine($"Middle: {string.Join(",", middle)}");
}8. required 成员
为什么:对象初始化器允许漏设关键属性,导致运行时错误。 解决痛点:编译时强制初始化必备成员。
// 之前:无法强制用户必须初始化某些属性
public class Person
{
public string Name { get; init; } // 可省略,导致对象不完整
}
// C# 11:标记为required,编译器强制初始化
public class Person
{
public required string Name { get; init; }
}
var p = new Person(); // ❌ 编译错误:未初始化Name
var p = new Person { Name = "Alice" }; // ✅ 正确C# 10 (.NET 6)
9. 全局 Using
为什么:每个文件重复相同的 using 声明,样板代码冗余。 解决痛点:项目级统一命名空间引用。
// GlobalUsings.cs(只需一个文件)
global using System.Collections.Generic;
global using System.Linq;
// 其他文件无需再写上述using,代码更简洁10. 文件范围的命名空间
为什么:单文件命名空间必须用大括号,增加缩进层级。 解决痛点:减少缩进,提升可读性。
// 之前:namespace增加一层缩进
namespace MyApp
{
class MyClass { } // 多一层缩进
}
// C# 10:文件范围命名空间
namespace MyApp; // 直接作用于整个文件
class MyClass { } // 少一层缩进C# 9 (.NET 5)
11. 记录(Record)类型
为什么:DTO/Data Model需要手动实现相等性、ToString、不可变性等功能。 解决痛点:一键生成不可变数据类型。
// 之前:手写DTO需要50行代码实现Equals、GetHashCode、ToString等
public class Person : IEquatable<Person>
{
public string Name { get; }
public int Age { get; }
// 手动实现相等性... 太啰嗦
}
// C# 9:一行搞定
public record Person(string Name, int Age); // 自动生成相等性、ToString、With表达式
var p1 = new Person("Alice", 30);
var p2 = p1 with { Age = 31 }; // 非破坏性修改12. Init-Only 属性
为什么:对象初始化器允许后续修改,无法保证不可变性。 解决痛点:初始化后不再可写。
// 之前:初始化后仍可修改
public class Person { public string Name { get; set; } }
var p = new Person { Name = "Alice" };
p.Name = "Bob"; // ❌ 意外修改
// C# 9:init后不可再赋值
public class Person { public string Name { get; init; } }
var p = new Person { Name = "Alice" };
// p.Name = "Bob"; // ❌ 编译错误C# 8 (.NET Core 3.0)
13. 可空引用类型(Nullable Reference Types)
为什么:空引用异常是历史上最常见的Bug,没有编译时检查。 解决痛点:编译时防空引用异常。
#nullable enable
// 之前:所有引用类型默认可空,运行时才能发现错误
string s = null; // 不报错,但后续s.Length会崩溃
// C# 8:声明意图,编译器帮你检查
string? s = null; // 明确可为空
Console.WriteLine(s.Length); // ⚠️ 编译警告:可能为空
string s2 = ""; // 不可空,赋值null会警告14. 异步流(IAsyncEnumerable)
为什么:IEnumerable<T> 无法支持异步数据流(如网络数据分页)。 解决痛点:异步生成数据序列。
// 之前:无法异步yield return
async Task<IEnumerable<int>> GetDataAsync()
{
await Task.Delay(100); // 无法边下载边返回数据
return new[] { 1, 2, 3 };
}
// C# 8:支持异步生成
async IAsyncEnumerable<int> GetDataAsync()
{
for (int i = 0; i < 3; i++)
{
await Task.Delay(100); // 每生成一个元素前都可await
yield return i; // 边下载边返回
}
}
// 调用:await foreach
await foreach (var item in GetDataAsync())
{
Console.WriteLine(item);
}15. 范围和索引(Ranges & Indices)
为什么:取数组子集需要写 Array.Copy 或手动计算索引,易出错。 解决痛点:提供直观语法访问集合片段。
// 之前:取最后3个元素
var arr = new[] { 0, 1, 2, 3, 4, 5 };
var last3 = arr.Skip(arr.Length - 3).ToArray(); // 啰嗦且分配内存
// C# 8:语法糖
var last = arr[^1]; // 最后一个元素
var slice = arr[^3..]; // 最后3个元素,零分配返回Span设计哲学总结
C# 新特性的演进始终围绕三个核心目标:
- 提升开发者生产力:减少样板代码(主构造函数、记录、全局using)
- 增强类型安全:编译期捕获更多错误(可空引用类型、required成员)
- 兼顾性能与抽象:零分配内存访问(Span/Memory)、高性能集合(内联数组)
这些特性共同让 C# 在保持高级语言易用性的同时,能胜任系统级高性能编程。
更早版本(简要)
- C# 7.0-7.3 (2017-2019):元组、模式匹配、ref 局部变量、
ValueTask - C# 6 (2015):字符串内插、表达式体成员、
nameof、null 传播运算符 - C# 5 (2012):
async/await - C# 4 (2010):动态类型、可选参数、命名参数
- C# 3 (2007):LINQ、Lambda 表达式、匿名类型
- C# 2 (2005):泛型、迭代器、
yield、null可空类型 - C# 1 (2002):基础面向对象特性
使用建议
- 新项目:优先使用 C# 12-13 特性(主构造函数、集合表达式)
- 异步编程:使用
IAsyncEnumerable(C# 8) 和Lock(C# 13) - 高性能:利用
Span<T>和InlineArray(C# 12) - 数据模型:使用记录 (C# 9) 和
required属性 (C# 11)
