Convertimos Stream en Byte Array: guía para desarrollador C# (edición 2025)
\n\n¿Alguna vez te has sorprendido pensando: «Bueno, ¿por qué C# no me deja simplemente tomar y convertir este Stream en un arreglo de bytes con una sola línea?». Si es así, no estás solo. La pregunta «Creating a byte array from a stream» en Stack Overflow acumula miles de visitas, y no es casualidad. Parece una tarea elemental, pero el diablo, como siempre, está en los detalles: rendimiento, memoria, asincronía y versiones de .NET.
En 2025, el ecosistema .NET nos ofrece varias soluciones elegantes. En este artículo, analizaremos las 5 mejores prácticas, desde los clásicos hasta las novedades. Aprenderás cuándo usar MemoryStream, por qué BinaryReader sigue siendo relevante y cómo no «tumbar» la aplicación al trabajar con flujos gigantescos. ¡Allá vamos!
1. Clásico del género: MemoryStream y CopyTo
\n\nLa forma más popular e intuitiva. Si tienes un Stream (por ejemplo, de un archivo, red o memoria), simplemente lo «vuelcas» en un MemoryStream y luego llamas a ToArray().
public static byte[] StreamToBytesUsingMemoryStream(Stream inputStream)\n{\n using (var memoryStream = new MemoryStream())\n {\n inputStream.CopyTo(memoryStream);\n return memoryStream.ToArray();\n }\n}\n\nVentajas:
\n- \n
- Simplicidad y legibilidad. \n
- Funciona en todas las versiones de .NET (Framework, Core, 5+). \n
- Soporta
CopyToAsyncpara trabajo asíncrono. \n
Desventajas:
\n- \n
- Si el flujo es enorme (por ejemplo, 1 GB), mantienes los datos en memoria dos veces: primero en
MemoryStream, luego en el arreglo. \n - No es la opción más rápida para flujos muy pequeños (sobrecarga por crear el objeto). \n
Cuándo usarlo: Para flujos de tamaño mediano (hasta 100 MB) y cuando la legibilidad del código es más importante que las microoptimizaciones.
\n\n2. Héroe asíncrono: ReadAsync y Buffer
\n\nEn el desarrollo moderno, la asincronía es el estándar. El método ReadAsync permite leer datos en porciones sin bloquear el hilo. Esto es especialmente útil en ASP.NET Core o aplicaciones GUI.
public static async Task<byte[]> StreamToBytesAsync(Stream inputStream)\n{\n using (var memoryStream = new MemoryStream())\n {\n byte[] buffer = new byte[8192]; // 8 KB — tamaño estándar\n int bytesRead;\n while ((bytesRead = await inputStream.ReadAsync(buffer, 0, buffer.Length)) > 0)\n {\n await memoryStream.WriteAsync(buffer, 0, bytesRead);\n }\n return memoryStream.ToArray();\n }\n}\n\nConsejo profesional: El tamaño del búfer se puede cambiar. Para flujos de red, 8 KB es el punto medio ideal. Para discos SSD, se puede aumentar a 64 KB.
\n\nVentajas:
\n- \n
- No bloquea el hilo llamante. \n
- Permite procesar datos «sobre la marcha» (por ejemplo, calcular un hash). \n
- Control sobre el consumo de memoria. \n
Desventajas:
\n- \n
- Un poco más de código. \n
- Requiere comprensión del patrón async/await. \n
3. BinaryReader: un viejo amigo es mejor que dos nuevos
\n\nMuchos olvidan BinaryReader, y es una lástima. Puede leer todo el flujo con un solo comando: ReadBytes. Pero hay un detalle: necesita conocer la longitud del flujo de antemano.
public static byte[] StreamToBytesUsingBinaryReader(Stream inputStream)\n{\n if (inputStream.CanSeek)\n {\n inputStream.Position = 0; // Reiniciar posición si es necesario\n using (var reader = new BinaryReader(inputStream, Encoding.UTF8, leaveOpen: true))\n {\n return reader.ReadBytes((int)inputStream.Length);\n }\n }\n else\n {\n throw new InvalidOperationException("El Stream no admite búsqueda");