C# Span<T> 与内存高效处理
学习使用 Span<T> 和 Memory<T> 进行无分配的内存切片操作,提升性能。 · 难度:入门 · +15XP
为什么需要 Span?
在 .NET 中,字符串操作、数组切片等通常会产生内存分配(如 Substring 创建新字符串)。Span<T> 是 C# 7.2 引入的值类型,提供安全的、无分配的内存视图,可以指向数组、堆栈内存或非托管内存。
基本用法
int[] numbers = { 1, 2, 3, 4, 5, 6 };
Span slice = numbers.AsSpan(1, 3); // 指向索引1开始的3个元素
slice[0] = 99; // 修改会反映到原数组
Console.WriteLine(string.Join(", ", numbers)); // 1, 99, 3, 4, 5, 6 Span 与字符串
string text = "Hello, World!";
ReadOnlySpan span = text.AsSpan();
ReadOnlySpan hello = span.Slice(0, 5);
Console.WriteLine(hello.ToString()); // "Hello" 性能对比
| 操作 | 传统方式 | Span 方式 |
|---|---|---|
| 子字符串 | Substring → 分配新字符串 | AsSpan().Slice() → 无分配 |
| 数组切片 | Array.Copy / LINQ Skip | AsSpan().Slice() |
| 解析数据 | Split → 分配数组 | Span 逐字符解析 |
Memory<T> 与 Span<T> 的区别
- Span<T>:值类型,只能存在于栈上,不能作为字段、不能装箱、不能用于异步方法。
- Memory<T>:引用类型,可以存储在堆上,用于异步场景。
async Task ProcessAsync(Memory memory)
{
// 异步方法中必须使用 Memory
await Task.Delay(100);
Span span = memory.Span; // 同步方法中获取 Span
} 实用场景
- 解析网络协议数据包
- 高效文本处理(如 CSV 解析)
- 图像处理像素操作
- 避免字符串分配
练习提示
- 使用 Span 实现一个不分配内存的字符串反转函数。
- 比较 Substring 和 Span.Slice 在大量调用时的内存分配差异。
- 用 Span 解析一个 CSV 行,提取字段而不产生分配。