Как создать массив байт (byte[]) из потока (Stream) в C#?
Решение: универсальный метод чтения потока в массив байт для .NET 3.5
Проблема, которую вы описали, действительно распространена. Основная сложность в том, что Stream.Read() не гарантирует чтение запрошенного количества байт за один вызов. Кроме того, свойство Length может быть недоступно для потоков без поддержки поиска (seeking).
Надежное решение — читать данные блоками в цикле, накапливая их в динамическом буфере. Для .NET 3.5 у нас нет MemoryStream.ToArray() в удобном виде, но мы можем использовать MemoryStream как промежуточный накопитель или собирать данные через List<byte>.
Вот два проверенных подхода:
Подход 1: Чтение через MemoryStream (рекомендуемый)
public static byte[] ReadFully(Stream input)
{
byte[] buffer = new byte[16 * 1024]; // 16 КБ — оптимальный размер буфера
using (MemoryStream ms = new MemoryStream())
{
int bytesRead;
while ((bytesRead = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, bytesRead);
}
return ms.ToArray();
}
}
Подход 2: Чтение через List<byte> (альтернатива)
public static byte[] ReadFullyList(Stream input)
{
byte[] buffer = new byte[16 * 1024];
List<byte> allBytes = new List<byte>();
int bytesRead;
while ((bytesRead = input.Read(buffer, 0, buffer.Length)) > 0)
{
// Добавляем только прочитанные байты
for (int i = 0; i < bytesRead; i++)
{
allBytes.Add(buffer[i]);
}
}
return allBytes.ToArray();
}
Что происходит под капотом?
- Буфер 16 КБ — хороший компромисс между производительностью и потреблением памяти. Слишком маленький буфер (например, 1 КБ) увеличит количество итераций, слишком большой (1 МБ) может создать лишнюю нагрузку на кучу.
- Цикл while гарантирует, что мы прочитаем все данные, даже если поток отдает их частями (типично для сети или сжатых данных).
- MemoryStream автоматически увеличивает свою внутреннюю емкость по мере записи, что эффективнее, чем ручное управление списком.
Оптимизация для потоков с известной длиной
Если вы уверены, что поток поддерживает Length и поиск (можно проверить через input.CanSeek), можно создать массив нужного размера заранее:
public static byte[] ReadFullyWithLength(Stream input)
{
if (input.CanSeek)
{
int length = (int)input.Length;
byte[] buffer = new byte[length];
int offset = 0;
int bytesRead;
while ((bytesRead = input.Read(buffer, offset, length - offset)) > 0)
{
offset += bytesRead;
}
return buffer;
}
else
{
return ReadFully(input); // fallback
}
}
Важные замечания для .NET 3.5
- Методы
CopyTo()иCopyToAsync()появились только в .NET 4.0, поэтому в вашем случае нужно использовать ручной цикл. - Всегда используйте
usingдля потоков, если вы их создаете, чтобы гарантировать освобождение ресурсов. - Для очень больших потоков (сотни мегабайт) рассмотрите возможность потоковой обработки, чтобы избежать загрузки всего содержимого в память.
Рекомендую использовать первый подход (через MemoryStream) — он простой, надежный и подходит для любых типов потоков.
Альтернативный подход: чтение с помощью BinaryReader
Можно использовать класс BinaryReader, который предоставляет удобный метод ReadBytes(Int32). Однако есть нюанс: если размер потока неизвестен, нужно передать максимальное значение, которое может прочитать BinaryReader (например, int.MaxValue).
public static byte[] ReadFullyWithBinaryReader(Stream input)
{
using (BinaryReader reader = new BinaryReader(input))
{
// Читаем все оставшиеся байты (оставьте поток открытым, если нужно)
const int bufferSize = 4096;
using (MemoryStream ms = new MemoryStream())
{
byte[] chunk;
while ((chunk = reader.ReadBytes(bufferSize)).Length > 0)
{
ms.Write(chunk, 0, chunk.Length);
}
return ms.ToArray();
}
}
}
Когда это может быть полезно?
- Если вы уже работаете с
BinaryReaderдля чтения других типов данных (int, string и т.д.), этот подход будет естественным продолжением. - Код выглядит чище, но имейте в виду:
BinaryReaderпо умолчанию закрывает поток при своем уничтожении (если не использовать конструктор с параметромleaveOpen: true, который недоступен в .NET 3.5).
Недостатки
BinaryReaderв .NET 3.5 не поддерживаетleaveOpen, поэтому поток будет закрыт после вызоваDispose().- Производительность может быть чуть ниже, чем у прямого чтения через
Stream.Read, из-за дополнительной обертки.
Для вашего случая (.NET 3.5) первый ответ с использованием MemoryStream и цикла — самый универсальный и безопасный вариант.
Ответов пока нет.
Войдите чтобы оставить ответ