CountBy、AggregateBy、Base64Url、Task.WhenEach、Lock 类型------.NET 9 标准库带来 20+ 实用 API
版本定位
适用版本:.NET 9 前置知识:LINQ 基础、集合操作
背景
.NET 9 不只是 C# 语言的更新,标准库也带来了一大批实用的新 API。从 LINQ 的 CountBy/AggregateBy 到安全领域的 BinaryFormatter 移除,从高性能的 SearchValues 增强到并发编程的 Task.WhenEach,这些 API 解决了长期以来的痛点,让代码更简洁、性能更好、安全性更强。
新增 API 一览
| API | 命名空间 | 用途 | 实用性 |
|---|---|---|---|
| CountBy | System.Linq | 按键计数,无需 GroupBy | ⭐⭐⭐⭐⭐ |
| AggregateBy | System.Linq | 按键聚合,无需 GroupBy | ⭐⭐⭐⭐⭐ |
| Index | System.Linq | 为元素添加索引 | ⭐⭐⭐⭐ |
| Base64Url | System.Buffers | URL 安全的 Base64 编解码 | ⭐⭐⭐⭐⭐ |
| Task.WhenEach | System.Threading.Tasks | 按完成顺序获取 Task 结果 | ⭐⭐⭐⭐⭐ |
| BinaryFormatter 移除 | --- | 安全:完全移除危险的序列化器 | ⭐⭐⭐⭐⭐ |
| Lock 类型 | System.Threading | 新的轻量级线程同步原语 | ⭐⭐⭐⭐ |
| OrderedDictionary<TKey,TValue> | System.Collections.Generic | 有序字典 | ⭐⭐⭐⭐ |
| Guid v7 | System | 时间有序的 GUID | ⭐⭐⭐⭐ |
| ServerSentEvents | System.Net.ServerSentEvents | SSE 编解码 | ⭐⭐⭐⭐ |
| SearchValues 增强 | System.Buffers | 更高性能的搜索 | ⭐⭐⭐⭐ |
| FrozenDictionary 增强 | System.Collections.Frozen | 批量添加、TryCreateFrozen | ⭐⭐⭐⭐ |
| Tensor<T> | System.Numerics.Tensors | AI 计算基块 | ⭐⭐⭐ |
| PriorityQueue.Remove | System.Collections.Generic | 从队列中移除元素 | ⭐⭐⭐⭐ |
| Parallel.ForEachAsync | System.Threading.Tasks | 异步并行ForEach | ⭐⭐⭐⭐ |
| Span/ReadOnlySpan 改进 | System | 隐式转换、新方法 | ⭐⭐⭐⭐ |
| System.Text.Json 改进 | System.Text.Json | 枚举值字符串、UnmappedMemberHandling | ⭐⭐⭐⭐ |
| CryptographicOperations | System.Security.Cryptography | 一次性哈希/验证 | ⭐⭐⭐⭐ |
| StringSyntax 增强 | System.Diagnostics.CodeAnalysis | 更多语法标记 | ⭐⭐⭐ |
| Polyfill 支持 | System.Runtime.CompilerServices | 内置 Polyfill | ⭐⭐⭐ |
| TimeSpan 从整数创建 | System | 避免浮点精度问题 | ⭐⭐⭐ |
| PersistedAssemblyBuilder | System.Reflection.Emit | 可保存的动态程序集 | ⭐⭐⭐ |
| Stopwatch 改进 | System.Diagnostics | GetTimestamp 高精度计时 | ⭐⭐⭐ |
一、LINQ 新方法详解
1. CountBy ------ 按键计数
之前的做法:GroupBy + Count
// .NET 8 及之前
var counts = list
.GroupBy(x => x.Category)
.Select(g => new { Category = g.Key, Count = g.Count() })
.ToDictionary(x => x.Category, x => x.Count);
// { "A": 3, "B": 2, "C": 1 }
.NET 9 的做法:一行搞定
var counts = list.CountBy(x => x.Category);
// { "A": 3, "B": 2, "C": 1 }
| 对比 | GroupBy + Count | CountBy |
|---|---|---|
| 代码行数 | 3-4 行 | 1 行 |
| 中间分配 | 多次 | 无 |
| 返回类型 | Dictionary | IEnumerable<KeyValuePair> |
2. AggregateBy ------ 按键聚合
// 旧做法
var totals = list
.GroupBy(x => x.Type)
.Select(g => new { Type = g.Key, Total = g.Sum(x => x.Amount) });
// .NET 9
var totals = list.AggregateBy(
keySelector: x => x.Type,
seed: 0m,
accumulator: (acc, item) => acc + item.Amount
);
更复杂的聚合:
var stats = list.AggregateBy(
keySelector: x => x.Category,
seed: new { Total = 0m, Count = 0 },
accumulator: (acc, item) => new
{
Total = acc.Total + item.Amount,
Count = acc.Count + 1
}
);
3. Index ------ 添加索引
// 旧做法
var indexed = list.Select((item, index) => new { Index = index, Item = item });
// .NET 9
var indexed = list.Index();
foreach (var (index, item) in indexed)
{
Console.WriteLine($"{index}: {item}");
}
二、高性能 API
4. Base64Url 编解码
痛点 :URL 中不能直接用标准 Base64(+、/、= 有特殊含义)
// 旧做法:手动替换
string ToBase64Url(byte[] data)
{
return Convert.ToBase64String(data)
.TrimEnd('=')
.Replace('+', '-')
.Replace('/', '_');
}
// .NET 9:内置支持
string encoded = Base64Url.EncodeToString(data);
byte[] decoded = Base64Url.FromBase64String(encoded);
// Span 版本(零分配)
int written = Base64Url.EncodeToUtf8(data, buffer);
典型场景:JWT token、URL 安全的 ID、加密数据传输
5. SearchValues 增强
// .NET 8:只支持字符和字节
var searchValues = SearchValues.Create("abc");
// .NET 9:支持更多类型
var intSearch = SearchValues.Create(new[] { 1, 2, 3 });
var utf8Search = SearchValues.Create("hello"u8);
// 使用
ReadOnlySpan<int> data = [1, 2, 3, 4, 5, 1, 2, 3];
int index = data.IndexOfAny(intSearch); // 返回 0
6. FrozenDictionary / FrozenSet 增强
// .NET 8:只能从 Dictionary 创建
var frozen = new Dictionary<string, int> { ["a"] = 1, ["b"] = 2 }
.ToFrozenDictionary();
// .NET 9:更多创建方式
var frozen2 = FrozenDictionary.ToFrozenDictionary(
[("a", 1), ("b", 2)]
);
// TryCreateFrozen(批量添加)
var result = FrozenDictionary.TryCreateFrozen(
[("a", 1), ("b", 2), ("a", 3)], // 重复键
EqualityComparer<string>.Default,
out var frozen3 // 只保留第一个 "a"
);
7. Span / ReadOnlySpan 改进
// .NET 9 新增的隐式转换
Span<int> span = new int[] { 1, 2, 3 }; // 数组到 Span 隐式转换
// 新增方法
ReadOnlySpan<char> span = "Hello, World!";
span.ContainsAny("aeiou"); // 检查是否包含任一字符
span.ContainsAny("aeiou".AsSpan());
三、并发与异步
8. Task.WhenEach ------ 按完成顺序
痛点 :Task.WhenAll 等所有完成才返回,无法按完成顺序处理
// 旧做法
var results = await Task.WhenAll(task1, task2, task3);
// 全部完成后才能处理
// .NET 9:按完成顺序逐个处理
await foreach (var task in Task.WhenEach(task1, task2, task3))
{
var result = await task;
Console.WriteLine($"完成: {result}");
// 第一个完成的任务立即处理,不等其他
}
典型场景:并发 HTTP 请求、批量任务处理、实时日志收集
9. Parallel.ForEachAsync 改进
// .NET 8
await Parallel.ForEachAsync(items, new ParallelOptions
{
MaxDegreeOfParallelism = 4
}, async (item, ct) =>
{
await ProcessAsync(item, ct);
});
// .NET 9:更简洁
await Parallel.ForEachAsync(items, async (item, ct) =>
{
await ProcessAsync(item, ct);
});
// 自动选择最优并行度
10. Lock 类型
痛点 :lock 语句使用 Monitor,无法跨 await 使用
// .NET 8 及之前
private readonly object _lock = new object();
lock (_lock)
{
// 不能 await
}
// .NET 9:新的 Lock 类型
private readonly Lock _lock = new Lock();
// 在同步代码中使用
lock (_lock)
{
_counter++;
}
// 在异步代码中使用
using (await _lock.EnterAsync())
{
await UpdateAsync();
}
四、集合改进
11. OrderedDictionary<TKey, TValue>
// .NET 8:没有内置的有序字典
// 需要自己实现或用第三方库
// .NET 9
var dict = new OrderedDictionary<string, int>();
dict.Add("first", 1);
dict.Add("second", 2);
dict.Add("third", 3);
// 保持插入顺序
foreach (var kvp in dict)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value}");
}
// 输出顺序:first, second, third
// 按索引访问
var item = dict[0]; // first: 1
12. PriorityQueue.Remove
var queue = new PriorityQueue<string, int>();
queue.Enqueue("low", 10);
queue.Enqueue("high", 1);
queue.Enqueue("medium", 5);
// 移除元素
bool removed = queue.Remove("high", out string? element, out int priority);
// removed = true, element = "high", priority = 1
13. Guid v7(时间有序)
// .NET 8:生成随机 GUID
var guid1 = Guid.NewGuid(); // 完全随机
// .NET 9:生成时间有序 GUID
var guid2 = Guid.CreateVersion7();
// 基于时间排序,适合作为数据库主键
// 从时间戳创建
var guid3 = Guid.CreateVersion7(DateTimeOffset.UtcNow);
优势:数据库索引友好、分布式系统中保持排序、兼容 v1/v4 格式
五、安全改进
14. BinaryFormatter 完全移除 ⚠️
这是 .NET 9 最重要的安全改进之一
// .NET 8:已标记为 obsolete
// [Obsolete] BinaryFormatter
// .NET 9:完全移除,无法使用
// var formatter = new BinaryFormatter(); // 编译错误
为什么移除? BinaryFormatter 存在严重的反序列化漏洞,已被大量 CVE 利用
替代方案:
| 场景 | 推荐方案 |
|---|---|
| JSON 序列化 | System.Text.Json |
| 二进制序列化 | protobuf-net |
| 高性能序列化 | MessagePack |
| .NET 内部序列化 | ISerializable + 自定义实现 |
迁移示例:
// 旧代码
using var formatter = new BinaryFormatter();
using var stream = new FileStream("data.bin", FileMode.Open);
var data = formatter.Deserialize(stream);
// 新代码
using var stream = new FileStream("data.json", FileMode.Open);
var data = await JsonSerializer.DeserializeAsync<MyData>(stream);
15. CET(Control-flow Enforcement Technology)影子栈
-
.NET 9 在支持 CET 的硬件上自动启用影子栈保护
-
防止 ROP(Return-Oriented Programming)攻击
-
对应用透明,无需代码修改
16. CryptographicOperations 改进
// 一次性哈希(更安全,避免复用)
byte[] hash = CryptographicOperations.HashData(
HashAlgorithmName.SHA256, data
);
// 一次性验证
bool valid = CryptographicOperations.VerifyHash(
HashAlgorithmName.SHA256,
data,
signature
);
六、其他重要 API
17. ServerSentEvents 编解码
// 旧做法:手动构建 SSE 格式
await response.WriteAsync("data: hello\n\n");
// .NET 9:内置 SSE 支持
using var writer = new SseWriter(response.Body);
await writer.WriteAsync(new SseMessage
{
EventType = "message",
Data = "hello"
});
18. System.Text.Json 改进
// 枚举值字符串(无额外分配)
var options = new JsonSerializerOptions
{
Converters = { new JsonStringEnumConverter() }
};
// UnmappedMemberHandling(严格模式)
var strictOptions = new JsonSerializerOptions
{
UnmappedMemberHandling = JsonUnmappedMemberHandling.Disallow
};
// 遇到未映射的 JSON 属性时抛出异常
19. Tensor<T>(AI 计算基块)
// 用于 AI/ML 计算的基础张量类型
var tensor = Tensor.Create<float>(new[] { 2, 3 }); // 2x3 矩阵
tensor[0, 0] = 1.0f;
tensor[0, 1] = 2.0f;
// 数学运算
var result = tensor + tensor;
var multiplied = tensor * 2.0f;
20. TimeSpan 从整数创建
// .NET 8:只支持 double
var ts = TimeSpan.FromSeconds(30.0);
// .NET 9:支持 int,避免浮点精度问题
var ts2 = TimeSpan.FromSeconds(30); // int 重载
var ts3 = TimeSpan.FromMilliseconds(500);
var ts4 = TimeSpan.FromTicks(10000);
21. StringSyntax 增强
// .NET 9 扩展了 StringSyntax 属性
public class MyOptions
{
[StringSyntax(StringSyntaxAttribute.Regex)]
public string? Pattern { get; set; }
[StringSyntax(StringSyntaxAttribute.Json)]
public string? Json { get; set; }
[StringSyntax(StringSyntaxAttribute.DateOnlyFormat)]
public string? DateFormat { get; set; }
[StringSyntax(StringSyntaxAttribute.TimeOnlyFormat)]
public string? TimeFormat { get; set; }
}
实战场景
CountBy + AggregateBy 组合
// 电商统计:每个类别的订单数和总金额
var stats = orders
.CountBy(o => o.Category)
.ToDictionary(x => x.Key, x => x.Value);
var totals = orders
.AggregateBy(
o => o.Category,
0m,
(total, order) => total + order.Amount
);
Task.WhenEach 实时处理
// 并发下载多个文件,按完成顺序保存
var downloads = urls.Select(url => HttpClient.GetAsync(url));
await foreach (var task in Task.WhenEach(downloads))
{
var response = await task;
var fileName = ExtractFileName(response.RequestMessage.RequestUri);
await SaveFileAsync(fileName, response.Content);
Console.WriteLine($"已保存: {fileName}");
}
Lock 替代 lock 语句
public class ThreadSafeCache<TKey, TValue>
{
private readonly Lock _lock = new();
private readonly Dictionary<TKey, TValue> _cache = new();
public async Task<TValue> GetOrAddAsync(TKey key, Func<Task<TValue>> factory)
{
using (await _lock.EnterAsync())
{
if (_cache.TryGetValue(key, out var value))
return value;
value = await factory();
_cache[key] = value;
return value;
}
}
}
BinaryFormatter 迁移
// 旧:BinaryFormatter + FileStream
// 新:System.Text.Json + FileStream
public static async Task SaveDataAsync<T>(string path, T data)
{
var options = new JsonSerializerOptions { WriteIndented = true };
await using var stream = File.Create(path);
await JsonSerializer.SerializeAsync(stream, data, options);
}
public static async Task<T?> LoadDataAsync<T>(string path)
{
await using var stream = File.OpenRead(path);
return await JsonSerializer.DeserializeAsync<T>(stream);
}
迁移建议
从 GroupBy 迁移到 CountBy/AggregateBy
// 旧代码
var counts = list.GroupBy(x => x.Key).ToDictionary(g => g.Key, g => g.Count());
// 新代码
var counts = list.CountBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value);
// 注意:CountBy 返回 IEnumerable<KeyValuePair>,如需 Dictionary 需 ToDictionary
BinaryFormatter 迁移检查清单
-
全局搜索
BinaryFormatter、FormatterAssemblyStyle -
替换为
System.Text.Json或protobuf-net -
测试所有序列化/反序列化路径
-
验证数据兼容性
注意事项
| API | 注意点 |
|---|---|
| CountBy | 返回 IEnumerable<KeyValuePair>,不是 Dictionary |
| Task.WhenEach | 需要 .NET 9+,不支持 netstandard |
| Lock | 不能替代 Monitor 的所有用法(如 TryEnter) |
| Guid v7 | 需要系统时钟,不保证全局唯一 |
| BinaryFormatter | .NET 9 完全移除,必须迁移 |
一句话总结
.NET 9 标准库带来 20+ 实用 API:LINQ 更简洁、并发更灵活、安全更可靠、性能更出色。