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

Span<T> 和 Memory<T> 是 C# 7.2 引入的核心高性能内存抽象类型,专为避免堆分配、减少 GC 压力、提升数据处理效率而设计。它们不是“替代数组”,而是更安全、更灵活的“内存视图”——能指向栈内存、堆数组、本机内存甚至只读数据,且全程不触发装箱或额外拷贝。
Span<T>:栈友好的零分配切片工具
Span<T> 是 ref-like 类型,只能在栈上声明(如局部变量、方法参数),不能作为字段、不能被装箱、不能跨 await 边界。它的价值在于对已有内存做“零成本切片”和“就地操作”。
- 从数组创建:Span<int> span = array.AsSpan(); —— 不复制,只是引用起始地址+长度
- 切片操作:span.Slice(2, 5) —— O(1) 时间,返回新 Span,原数据不动
- 配合 stackalloc:Span<byte> buffer = stackalloc byte[256]; —— 栈上分配,无 GC 开销
- 写入字符串字节(UTF8):Utf8Encoder.Encode(buffer, "hello", out int written);
Memory<T>:可跨作用域的托管内存视图
Memory<T> 是 Span<T> 的“托管友好版”,可作为字段、参数、返回值,甚至用于 async 方法。它背后封装了 IMemoryOwner<T> 或数组等资源,但本身不拥有内存。
DMap GL地图JSAPI(dmapgl)开发指南。用于编写、审查、调试 DMap 地图 API 代码,涵盖地图初始化、覆盖物渲染、图层管理、事件处理、控件交互等任务。当用户提及 dmap、dmapgl、jsapi‑gl 或相关地图开发需求时自动触发。
- 从数组创建:Memory<int> mem = array; 或 array.AsMemory()
- 获取可写 Span:Span<int> span = mem.Span; —— 安全转为栈视图操作
- 异步场景示例:async Task ProcessAsync(Memory<byte> data) —— 调用方传入,方法内用 .Span 处理
- 注意:Memory<T> 本身不可序列化,也不直接暴露指针,安全性高于裸指针
常见高性能组合模式
真实项目中,Span/Memory 往往和特定 API 协同使用,才能释放最大效能。
- 字符串解析(不用 Split/ToArray):用 ReadOnlySpan<char> 遍历、IndexOf、Slice,跳过所有字符串分配
- 二进制协议处理:Socket.ReceiveAsync 返回 Memory<byte>,直接用 BinaryPrimitives 读取 int/long,避免 ArraySegment<byte> 封装开销
- 池化 + Memory:搭配 ArrayPool<byte>.Shared.Rent() 获取数组 → 转为 Memory<byte> → 处理完 Return(),复用缓冲区
- 只读场景优先用 ReadOnlySpan<T>/ReadOnlyMemory<T>:编译器会阻止意外写入,语义清晰,性能一致
必须避开的坑
Span 和 Memory 强大,但违反规则会导致编译失败或运行时异常(如 System.SpanHelpers.ThrowInvalidOperationIfNotInitialized)。
- Span<T> 不能存为类字段 —— 编译器直接报错:“Cannot declare a variable of type 'Span<int>' in a context where it cannot be used”
- 不能在 async 方法中 await 后继续用之前捕获的 Span —— 因为可能跨栈帧,生命周期失控;应改用 Memory<T>
- stackalloc 分配过大(如 >1MB)可能栈溢出 —— 生产环境慎用超大 stackalloc,优先考虑 ArrayPool
- 不要把 Span 暴露给不受信代码 —— 它可绕过部分数组边界检查(虽仍受 CLR 内存保护)
基本上就这些。Span 和 Memory 不是炫技工具,而是当你真在处理高频日志解析、网络包拆包、图像像素遍历、JSON 流式反序列化等场景时,最值得投入理解的底层利器。用对了,性能提升常是 2–5 倍,GC 次数直线下降。


















