Memory 是 .NET Core 2.1+ 引入的轻量级内存抽象,用于表示可读写的连续内存视图,支持跨 async 边界传递;适用于高性能 I/O 缓冲复用、序列化中间层等场景,但不可替代 List 或长期持有数据。

Memory 是什么,什么时候该用它
Memory 是 .NET Core 2.1+ 引入的轻量级内存抽象,用于表示一段可读写的连续内存(可以是托管数组、堆栈内存、本机内存等)。它本身不拥有内存,只持有“视图”——类似 Span,但支持跨 async 边界传递(Span 不能作为字段或异步方法参数)。
适合场景包括:高性能 I/O 缓冲区复用、序列化/反序列化中间层、零拷贝字符串处理、自定义协议解析。不适合直接替代 List 或长期持有数据。
如何从常见来源创建 Memory创建方式决定底层内存生命周期和性能特征,选错容易引发 ObjectDisposedException 或意外 GC 压力:
- 从数组:
Memory mem = new byte[1024]; → 安全,但数组仍受 GC 管理
- 从堆栈分配(仅限
Span,需转为 Memory 时注意):Span stackSpan = stackalloc byte[256]; Memory mem = stackSpan; → 仅限同步本地作用域,不可传出方法
- 从
ArrayPool.Shared.Rent() :Memory mem = ArrayPool.Shared.Rent(4096); → 推荐用于高频短时缓冲,记得 .Return()
- 从
UnmanagedMemoryStream 或 NativeMemory.Allocate() → 需手动管理生命周期,易出错,慎用
切片、复制与避免隐式分配
Memory 的 Slice() 是 O(1) 操作,不复制数据;但 ToArray() 或 ToString()(对 Memory)会触发堆分配,破坏零拷贝目标:
- 安全切片:
Memory header = packet.Slice(0, 4);
- 危险复制:
byte[] copy = packet.ToArray(); → 新数组,GC 压力
- 替代方案:
var reader = new BinaryReader(packet.AsStream()); → 复用流接口,避免中间数组
- 写入时优先用
Span 方法:mem.Span.Fill(0);、mem.Span.CopyTo(dest.Span);
异步方法中使用 Memory 的关键约束
Memory 可作为 async 方法参数/返回值,但必须确保其背后内存在整个异步操作期间有效。常见陷阱:
- 不要把局部
stackalloc 转成 Memory 后传入 await → 堆栈帧已销毁
- 不要在
using var array = new byte[...] 块内创建 Memory 并 await → array 可能被回收
- 推荐模式:
Memory buffer = ArrayPool.Shared.Rent(size); try { await stream.ReadAsync(buffer); } finally { ArrayPool.Shared.Return(buffer); }
- 若需长期持有,考虑
MemoryManager 自定义实现,但复杂度陡增
真正难的不是调用 Memory 的 API,而是判断哪段内存归谁管、生命周期是否对齐、以及何时该退回到 ArrayPool + Span 组合。多数性能问题其实出在“以为用了 Memory 就自动高效”,而忽略了背后内存的归属和释放时机。
Memory 是 .NET Core 2.1+ 引入的轻量级内存抽象,用于表示一段可读写的连续内存(可以是托管数组、堆栈内存、本机内存等)。它本身不拥有内存,只持有“视图”——类似 Span,但支持跨 async 边界传递(Span 不能作为字段或异步方法参数)。
适合场景包括:高性能 I/O 缓冲区复用、序列化/反序列化中间层、零拷贝字符串处理、自定义协议解析。不适合直接替代 List 或长期持有数据。
如何从常见来源创建 Memory创建方式决定底层内存生命周期和性能特征,选错容易引发 ObjectDisposedException 或意外 GC 压力:
- 从数组:
Memory mem = new byte[1024]; → 安全,但数组仍受 GC 管理
- 从堆栈分配(仅限
Span,需转为 Memory 时注意):Span stackSpan = stackalloc byte[256]; Memory mem = stackSpan; → 仅限同步本地作用域,不可传出方法
- 从
ArrayPool.Shared.Rent() :Memory mem = ArrayPool.Shared.Rent(4096); → 推荐用于高频短时缓冲,记得 .Return()
- 从
UnmanagedMemoryStream 或 NativeMemory.Allocate() → 需手动管理生命周期,易出错,慎用
切片、复制与避免隐式分配
Memory 的 Slice() 是 O(1) 操作,不复制数据;但 ToArray() 或 ToString()(对 Memory)会触发堆分配,破坏零拷贝目标:
- 安全切片:
Memory header = packet.Slice(0, 4);
- 危险复制:
byte[] copy = packet.ToArray(); → 新数组,GC 压力
- 替代方案:
var reader = new BinaryReader(packet.AsStream()); → 复用流接口,避免中间数组
- 写入时优先用
Span 方法:mem.Span.Fill(0);、mem.Span.CopyTo(dest.Span);
异步方法中使用 Memory 的关键约束
Memory 可作为 async 方法参数/返回值,但必须确保其背后内存在整个异步操作期间有效。常见陷阱:
- 不要把局部
stackalloc 转成 Memory 后传入 await → 堆栈帧已销毁
- 不要在
using var array = new byte[...] 块内创建 Memory 并 await → array 可能被回收
- 推荐模式:
Memory buffer = ArrayPool.Shared.Rent(size); try { await stream.ReadAsync(buffer); } finally { ArrayPool.Shared.Return(buffer); }
- 若需长期持有,考虑
MemoryManager 自定义实现,但复杂度陡增
真正难的不是调用 Memory 的 API,而是判断哪段内存归谁管、生命周期是否对齐、以及何时该退回到 ArrayPool + Span 组合。多数性能问题其实出在“以为用了 Memory 就自动高效”,而忽略了背后内存的归属和释放时机。
创建方式决定底层内存生命周期和性能特征,选错容易引发 ObjectDisposedException 或意外 GC 压力:
- 从数组:
Memory→ 安全,但数组仍受 GC 管理mem = new byte[1024]; - 从堆栈分配(仅限
Span,需转为Memory时注意):Span→ 仅限同步本地作用域,不可传出方法stackSpan = stackalloc byte[256]; Memory mem = stackSpan; - 从
ArrayPool:.Shared.Rent() Memory→ 推荐用于高频短时缓冲,记得mem = ArrayPool .Shared.Rent(4096); .Return() - 从
UnmanagedMemoryStream或NativeMemory.Allocate()→ 需手动管理生命周期,易出错,慎用
切片、复制与避免隐式分配
Memory 的 Slice() 是 O(1) 操作,不复制数据;但 ToArray() 或 ToString()(对 Memory)会触发堆分配,破坏零拷贝目标:
- 安全切片:
Memoryheader = packet.Slice(0, 4); - 危险复制:
byte[] copy = packet.ToArray();→ 新数组,GC 压力 - 替代方案:
var reader = new BinaryReader(packet.AsStream());→ 复用流接口,避免中间数组 - 写入时优先用
Span方法:mem.Span.Fill(0);、mem.Span.CopyTo(dest.Span);
异步方法中使用 Memory 的关键约束
Memory 可作为 async 方法参数/返回值,但必须确保其背后内存在整个异步操作期间有效。常见陷阱:
- 不要把局部
stackalloc 转成 Memory 后传入 await → 堆栈帧已销毁
- 不要在
using var array = new byte[...] 块内创建 Memory 并 await → array 可能被回收
- 推荐模式:
Memory buffer = ArrayPool.Shared.Rent(size); try { await stream.ReadAsync(buffer); } finally { ArrayPool.Shared.Return(buffer); }
- 若需长期持有,考虑
MemoryManager 自定义实现,但复杂度陡增
真正难的不是调用 Memory 的 API,而是判断哪段内存归谁管、生命周期是否对齐、以及何时该退回到 ArrayPool + Span 组合。多数性能问题其实出在“以为用了 Memory 就自动高效”,而忽略了背后内存的归属和释放时机。
Memory 可作为 async 方法参数/返回值,但必须确保其背后内存在整个异步操作期间有效。常见陷阱:
- 不要把局部
stackalloc转成Memory后传入await→ 堆栈帧已销毁 - 不要在
using var array = new byte[...]块内创建Memory并 await →array可能被回收 - 推荐模式:
Memorybuffer = ArrayPool .Shared.Rent(size); try { await stream.ReadAsync(buffer); } finally { ArrayPool .Shared.Return(buffer); } - 若需长期持有,考虑
MemoryManager自定义实现,但复杂度陡增
真正难的不是调用 Memory 的 API,而是判断哪段内存归谁管、生命周期是否对齐、以及何时该退回到 ArrayPool + Span 组合。多数性能问题其实出在“以为用了 Memory 就自动高效”,而忽略了背后内存的归属和释放时机。










