Span和Memory是C#7.2引入的高性能内存抽象,前者为栈上零分配视图,后者为可跨作用域的托管视图,二者均避免堆分配、减少GC压力,提升数据处理效率。

Span
Span:栈友好的零分配切片工具
Span
- 从数组创建:Span
span = array.AsSpan(); —— 不复制,只是引用起始地址+长度 - 切片操作:span.Slice(2, 5) —— O(1) 时间,返回新 Span,原数据不动
- 配合 stackalloc:Span
buffer = stackalloc byte[256]; —— 栈上分配,无 GC 开销 - 写入字符串字节(UTF8):Utf8Encoder.Encode(buffer, "hello", out int written);
Memory:可跨作用域的托管内存视图
Memory
- 从数组创建:Memory
mem = array; 或 array.AsMemory() - 获取可写 Span:Span
span = mem.Span; —— 安全转为栈视图操作 - 异步场景示例:async Task ProcessAsync(Memory
data) —— 调用方传入,方法内用 .Span 处理 - 注意:Memory
本身不可序列化,也不直接暴露指针,安全性高于裸指针
常见高性能组合模式
真实项目中,Span/Memory 往往和特定 API 协同使用,才能释放最大效能。
-
字符串解析(不用 Split/ToArray):用 ReadOnlySpan
遍历、IndexOf、Slice,跳过所有字符串分配 -
二进制协议处理:Socket.ReceiveAsync 返回 Memory
,直接用 BinaryPrimitives 读取 int/long,避免 ArraySegment 封装开销 -
池化 + Memory:搭配 ArrayPool
.Shared.Rent() 获取数组 → 转为 Memory→ 处理完 Return(),复用缓冲区 -
只读场景优先用 ReadOnlySpan
/ReadOnlyMemory 编译器会阻止意外写入,语义清晰,性能一致:
必须避开的坑
Span 和 Memory 强大,但违反规则会导致编译失败或运行时异常(如 System.SpanHelpers.ThrowInvalidOperationIfNotInitialized)。
-
Span
不能存为类字段 —— 编译器直接报错:“Cannot declare a variable of type 'Span' in a context where it cannot be used” -
不能在 async 方法中 await 后继续用之前捕获的 Span —— 因为可能跨栈帧,生命周期失控;应改用 Memory
- stackalloc 分配过大(如 >1MB)可能栈溢出 —— 生产环境慎用超大 stackalloc,优先考虑 ArrayPool
- 不要把 Span 暴露给不受信代码 —— 它可绕过部分数组边界检查(虽仍受 CLR 内存保护)
基本上就这些。Span 和 Memory 不是炫技工具,而是当你真在处理高频日志解析、网络包拆包、图像像素遍历、JSON 流式反序列化等场景时,最值得投入理解的底层利器。用对了,性能提升常是 2–5 倍,GC 次数直线下降。











