C# Memory T使用方法 C#如何高效处理内存块

来源:这里教程网 时间:2026-02-21 17:40:04 作者:

Memory 是什么,什么时候该用它

Memory<t></t>
是 .NET Core 2.1+ 引入的轻量级内存抽象,用于表示一段可读写的连续内存(可以是托管数组、堆栈内存、本机内存等)。它本身不拥有内存,只持有“视图”——类似
Span<t></t>
,但支持跨
async
边界传递(
Span<t></t>
不能作为字段或异步方法参数)。

适合场景包括:高性能 I/O 缓冲区复用、序列化/反序列化中间层、零拷贝字符串处理、自定义协议解析。不适合直接替代

List<t></t>
或长期持有数据。

如何从常见来源创建 Memory

创建方式决定底层内存生命周期和性能特征,选错容易引发

ObjectDisposedException
或意外 GC 压力:

从数组:
Memory<t> mem = new byte[1024];</t>
→ 安全,但数组仍受 GC 管理
从堆栈分配(仅限
Span<t></t>
,需转为
Memory<t></t>
时注意):
Span<byte> stackSpan = stackalloc byte[256]; Memory<byte> mem = stackSpan;</byte></byte>
→ 仅限同步本地作用域,不可传出方法
ArrayPool<byte>.Shared.Rent()</byte>
Memory<byte> mem = ArrayPool<byte>.Shared.Rent(4096);</byte></byte>
→ 推荐用于高频短时缓冲,记得
.Return()
UnmanagedMemoryStream
NativeMemory.Allocate()
→ 需手动管理生命周期,易出错,慎用

切片、复制与避免隐式分配

Memory<t></t>
Slice()
是 O(1) 操作,不复制数据;但
ToArray()
ToString()
(对
Memory<char></char>
)会触发堆分配,破坏零拷贝目标:

安全切片:
Memory<byte> header = packet.Slice(0, 4);</byte>
危险复制:
byte[] copy = packet.ToArray();
→ 新数组,GC 压力
替代方案:
var reader = new BinaryReader(packet.AsStream());
→ 复用流接口,避免中间数组
写入时优先用
Span<t></t>
方法:
mem.Span.Fill(0);
mem.Span.CopyTo(dest.Span);

异步方法中使用 Memory 的关键约束

Memory<t></t>
可作为 async 方法参数/返回值,但必须确保其背后内存在整个异步操作期间有效。常见陷阱:

不要把局部
stackalloc
转成
Memory<t></t>
后传入
await
→ 堆栈帧已销毁
不要在
using var array = new byte[...]
块内创建
Memory<byte></byte>
并 await →
array
可能被回收
推荐模式:
Memory<byte> buffer = ArrayPool<byte>.Shared.Rent(size); try { await stream.ReadAsync(buffer); } finally { ArrayPool<byte>.Shared.Return(buffer); }</byte></byte></byte>
若需长期持有,考虑
MemoryManager<t></t>
自定义实现,但复杂度陡增

真正难的不是调用

Memory<t></t>
的 API,而是判断哪段内存归谁管、生命周期是否对齐、以及何时该退回到
ArrayPool
+
Span<t></t>
组合。多数性能问题其实出在“以为用了
Memory<t></t>
就自动高效”,而忽略了背后内存的归属和释放时机。

相关推荐