将 Stream 转换为字节数组:C# 开发者指南(2025 版)
您是否曾有过这样的想法:“为什么,C#,你就不能让我用一行代码就把这个 Stream 转换成字节数组呢?”。如果是这样——您并不孤单。在 Stack Overflow 上,“从 stream 创建字节数组”这个问题获得了数千次浏览,这并非偶然。看似一个基本任务,但魔鬼总在细节中:性能、内存、异步性和 .NET 版本。
在 2025 年,.NET 生态系统为我们提供了几种优雅的解决方案。在本文中,我们将探讨 5 种最佳实践——从经典方法到新方法。您将了解何时使用 MemoryStream,为什么 BinaryReader 仍然适用,以及如何处理大型流而不会导致应用程序崩溃。让我们开始吧!
1. 经典方法:MemoryStream 和 CopyTo
最流行且最直观的方法。如果您有一个 Stream(例如,来自文件、网络或内存),您只需将其“复制”到 MemoryStream,然后调用 ToArray()。
public static byte[] StreamToBytesUsingMemoryStream(Stream inputStream){ using (var memoryStream = new MemoryStream()) { inputStream.CopyTo(memoryStream); return memoryStream.ToArray(); }}优点:
- 简单且可读性强。
- 适用于所有 .NET 版本(Framework、Core、5+)。
- 支持
CopyToAsync进行异步操作。
缺点:
- 如果流很大(例如 1 GB),数据会在内存中保存两次:一次在
MemoryStream中,一次在数组中。 - 对于非常小的流来说不是最快的选择(创建对象的开销)。
何时使用: 适用于中等大小的流(最多 100 MB),并且代码可读性比微优化更重要时。
2. 异步英雄:ReadAsync 和 Buffer
在现代开发中,异步是标准。 ReadAsync 方法允许您分块读取数据,而不会阻塞线程。这在 ASP.NET Core 或 GUI 应用程序中特别有用。
public static async Task<byte[]> StreamToBytesAsync(Stream inputStream){ using (var memoryStream = new MemoryStream()) { byte[] buffer = new byte[8192]; // 8 KB — 标准大小 int bytesRead; while ((bytesRead = await inputStream.ReadAsync(buffer, 0, buffer.Length)) > 0) { await memoryStream.WriteAsync(buffer, 0, bytesRead); } return memoryStream.ToArray(); }}专业建议: 缓冲区大小可以更改。对于网络流,8 KB 是黄金分割点。对于 SSD 磁盘,可以增加到 64 KB。
优点:
- 不阻塞调用线程。
- 允许“即时”处理数据(例如,计算哈希值)。
- 控制内存消耗。
缺点:
- 代码稍多。
- 需要理解 async/await 模式。
3. BinaryReader:老朋友胜过新朋友
许多人忘记了 BinaryReader,但这是错误的。它可以用一个命令读取整个流——ReadBytes。但有一个问题:它需要预先知道流的长度。
public static byte[] StreamToBytesUsingBinaryReader(Stream inputStream){ if (inputStream.CanSeek) { inputStream.Position = 0; // 如果需要,重置位置 using (var reader = new BinaryReader(inputStream, Encoding.UTF8, leaveOpen: true)) { return reader.ReadBytes((int)inputStream.Length); } } else { throw new InvalidOperationException("Stream 不支持查找