MemoryStream读写前须检查CanRead/CanWrite属性,用只读数组构造时默认不可写;写入后需Seek(0, SeekOrigin.Begin)重置位置才能读取;Dispose()不释放内存但应遵循using规范。

MemoryStream 读写前必须明确是否可读/可写
默认构造的 MemoryStream 是可读可写的,但用字节数组构造时(new MemoryStream(byte[]))会变成只读——哪怕数组本身可变。这是最常踩的坑:调用 Write() 或 SetLength() 直接抛 NotSupportedException。
实操建议:
- 需要写入 → 用无参构造或传入可修改的
byte[]并设writable: true(.NET Core 2.1+ 支持) - 仅读取已有数据 → 用
new MemoryStream(readonlyArray)更安全 - 不确定来源时,先检查
CanWrite和CanRead属性,别硬调
写入后记得 Seek(0, SeekOrigin.Begin) 才能重新读
MemoryStream 内部有位置指针(Position),写完数据后指针停在末尾。此时直接 Read() 会返回 0 字节——不是空,是“从末尾开始读”。
常见错误现象:序列化对象到 MemoryStream,不重置位置就交给 StreamReader 或反序列化器,结果读不到内容。
实操建议:
- 写完立即调用
stream.Position = 0或stream.Seek(0, SeekOrigin.Begin) - 如果后续只读不写,可用
stream.GetBuffer()获取底层数组(注意:可能含未使用填充字节,得配合stream.Length截取) - 更推荐
stream.ToArray()——它返回精确长度的新数组,安全但有拷贝开销
Dispose() 不释放内存,但不调可能泄漏非托管资源
MemoryStream 没有非托管句柄,所以不 Dispose() 不会导致内存泄漏。但它继承自 Stream,而某些上层 API(如 HttpClient.SendAsync() 的 HttpContent)会强制调用 Dispose()。如果子类被重写且依赖 Dispose() 清理,跳过就会出问题。
实操建议:
- 始终用
using块包裹,符合 .NET 资源管理约定 - 不要手动调
GC.Collect()强制回收——MemoryStream占用的是托管堆,GC 自会处理 - 大内存流(百 MB 级)可考虑
ArrayPool配合自定义流,避免频繁 GC 压力.Shared.Rent()
和 FileStream、NetworkStream 的行为差异点
MemoryStream 是纯内存操作,没有 I/O 延迟,但也不支持异步等待(ReadAsync / WriteAsync 在它内部只是同步调用的包装)。这点容易误导——以为加 await 就能提升性能,实际没意义。
实操建议:
- 别对
MemoryStream做await stream.ReadAsync(...),直接用同步方法更清晰 - 若需统一处理多种流类型(比如封装通用序列化方法),应判断
stream is MemoryStream分支走同步逻辑 - 跨线程共享
MemoryStream时,它本身不线程安全——读写不能并发,需自行加锁或用ConcurrentQueue等替代方案









