Updated on
To convert a stream to a byte array in C# we have to read the stream to the end, because a Stream is a cursor over a sequence of bytes and not a container of them. It has a position, it hands out bytes from that position forward, and in the general case it does not know how many are left.
On .NET 7 and later, one call does it when the stream reports a usable Length: allocate an array of that size and fill it with ReadExactly(). When the length is unknown, copy the stream into a MemoryStream with CopyTo() and call ToArray().
Everything below is the detail behind those two lines: why a single Read() call is not enough, what BinaryReader, StreamReader, MemoryStream and BufferedStream each cost, and what the benchmark says.
Preparation of Data Source
To begin, let’s add a text file to our project:
According to a report by CMGSMA, the mobile industry in Codemaze City has seen mobile broadband connections grow from 10% in 2000 to 74% in 2023.
Now, let’s use this file to create a FileStream object:
private static readonly string _sampleFilePath
= Path.Combine(Directory.GetParent(Directory.GetCurrentDirectory()).Parent.Parent.FullName, "samplefile.txt");
private readonly FileStream _stream
= new(_sampleFilePath, FileMode.Open, FileAccess.Read);
We aim to implement methods that convert _stream to a byte array.
That is the test fixture, and it reads a 145-byte sample file. The benchmark at the end of the article runs against a different and much larger file, a roughly 718 KB benchmarkfile.txt built by repeating the line above, and every benchmark method opens its own fresh FileStream so each iteration starts at position zero.
What Is the Simplest Way to Convert a Stream to a Byte Array in C#?
A stream does not reliably carry its own length, so converting one to a byte array means reading until the data runs out. Two calls cover almost every case.
When the stream is seekable and reports a trustworthy Length, allocate one array of that size and fill it with ReadExactly(). It repeats the read internally until the buffer is full, and throws EndOfStreamException if the stream ends first, so a short read becomes an error rather than a quietly truncated array.
When the length is unknown, which is normal for network, pipe and compressed streams, copy the stream into a MemoryStream with CopyTo() and call ToArray() on it. That costs a second copy of the data in memory, and it needs no length at all.
Both start at the stream’s current position. On a stream something has already read from, CopyTo() gives back only the remainder and ReadExactly() throws, because the stream ends before the array is full, so rewinding first is the caller’s job.
Let’s start with the known-length case:
public byte[] UseReadExactly(Stream stream)
{
var bytes = new byte[stream.Length];
stream.ReadExactly(bytes);
return bytes;
}
And when the length is unknown, we let CopyTo() do the reading:
public byte[] UseCopyTo(Stream stream)
{
using var memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
If the stream is a file we opened ourselves, we do not need a stream at all: File.ReadAllBytes() reads the whole file in one call, and converting a file to a byte array covers that case.
How Do We Convert a Stream With Stream.Read()?
Stream.Read() is allowed to return fewer bytes than we ask for. Its return value is the number of bytes it actually placed in the buffer, and that number is a promise of progress, not of completion. A file stream may hand back one disk block, and a network stream hands back whatever has arrived so far.
A single call is therefore never enough. The loop below keeps calling Read() until it returns zero, which is the only signal that the stream has no more data to give.
The runtime does this for us. ReadExactly() performs the same repeated reading internally and throws when the stream ends early, so the hand written loop survives here to show what the framework now does, not as the code to copy.
The expensive part is not the loop. It is the List<byte> and the 32 byte buffer, which is why this method finishes last in the benchmark.

This is the mechanism, not the recommendation. On .NET 7 and later, use ReadExactly() for a known length.
To ensure that we accurately read and convert all the data in the stream with the Stream.Read() method, we can create a method that reads and copies the data repeatedly until there is no more data left to be read:
public byte[] UseStreamDotReadMethod(Stream stream)
{
byte[] bytes;
List<byte> totalStream = new();
byte[] buffer = new byte[32];
int read;
while ((read = stream.Read(buffer, 0, buffer.Length)) > 0)
{
totalStream.AddRange(buffer.Take(read));
}
bytes = totalStream.ToArray();
return bytes;
}
In this method, we start by initializing totalStream, a List<byte> object, and a buffer array of bytes with a size of 32.
Next, we define read, an integer variable that will store the number of bytes read from the stream. Then we use a while loop to repeatedly read data from the stream into the buffer array until no more bytes are left to read.
In the while condition, we check the value of the read variable. If it is greater than zero, we add the bytes that were read to the totalStream list using the AddRange() method, which appends them to the end of the list. We use the Take() method to select only the number of bytes read and avoid adding stale bytes left in the buffer to our totalStream list.
Finally, we return the contents of the totalStream list as a byte array using the ToArray() method.
How Do We Convert a Stream With BinaryReader?
An easier way of converting a stream to a byte array in C#, is by using the BinaryReader class. This class allows us to read binary data from a stream and convert it into a byte array. Unlike Stream.Read(), BinaryReader.ReadBytes() keeps reading until it has the number of bytes we asked for or the stream ends, so one call is enough.
To see how this works, let’s create a UseBinaryReader() method:
public byte[] UseBinaryReader(Stream stream)
{
byte[] bytes;
using (var binaryReader = new BinaryReader(stream))
{
bytes = binaryReader.ReadBytes((int)stream.Length);
}
return bytes;
}
Here, we instantiate a BinaryReader object with the stream we want to read from. Next, we use the ReadBytes() method of the BinaryReader class to read the bytes from the stream and convert them into a byte array.
The ReadBytes() method takes an integer parameter that specifies the number of bytes to read from the stream. In this case, we aim to read all the bytes in the stream. Therefore, we pass in the length of the stream as an argument.
This needs a stream that reports its Length, and the cast to int caps it at about 2 GB, so it is not the method for a network stream or a very large file.
Finally, we return the byte array containing the data from the stream.
How Do We Convert a Stream With StreamReader?
Alternatively, we can convert a stream to a byte array with the StreamReader class. This class allows us to easily read textual data from a stream, which we then convert to a byte array.
To use the StreamReader class for this purpose, let’s define a UseStreamReader() method.
This one is here for completeness and should not be used to get bytes: it decodes the stream as text and encodes it again, which silently changes any data that is not UTF-8 text without a byte order mark:
public byte[] UseStreamReader(Stream stream)
{
byte[] bytes;
using (var reader = new StreamReader(stream))
{
bytes = System.Text.Encoding.UTF8.GetBytes(reader.ReadToEnd());
}
return bytes;
}
First, we create an instance of the StreamReader class and pass in the original stream as an argument. Then, we call the ReadToEnd() method on the reader object to read all the data from the stream as a string.
Finally, we use the GetBytes() method of the UTF8 encoding class to convert the string to a byte array and return it to the caller.
This method assumes that the stream contains textual data encoded in UTF8. If the data is in a different encoding, we should pass the corresponding encoding to the StreamReader constructor and use it instead of UTF8, and if what we actually have is a string rather than a stream, converting a string to a byte array is the shorter road.
How Do We Convert a Stream With MemoryStream?
Additionally, we can use the MemoryStream class to convert streams to byte arrays. This class handles streams of unknown length, at the cost of extra memory during the conversion, especially when working with large data streams.
CopyTo() moves the data a block at a time, so the source stream is never fully held in a buffer of our own. The MemoryStream itself still grows to hold the whole payload, doubling its capacity as it goes, and ToArray() then copies it again, which is why this method allocates more than one that sizes an array up front.
With this in mind, let’s define a UseMemoryStream() method:
public byte[] UseMemoryStream(Stream stream)
{
byte[] bytes;
if (stream is MemoryStream memStream)
{
return memStream.ToArray();
}
using (var memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
bytes = memoryStream.ToArray();
}
return bytes;
}
Here, we use an if statement to check if the stream we want to convert is already a MemoryStream. If it is, we call the ToArray() method on the stream and return the resulting byte array.
However, if the stream is not a MemoryStream, we create a new instance of the MemoryStream class. Then, we use the CopyTo() method to copy the contents of the original stream into the memory stream. Subsequently, we invoke the ToArray() method on the memory stream to get the byte array representation of the stream.
How Do We Convert a Stream With BufferedStream?
Now, let’s explore another method that involves using the BufferedStream class.
The BufferedStream class provides a way to improve the performance of a stream by incorporating a buffer.
We use a buffer to temporarily store data when transferring between components in a computer system with different speeds. This momentary storage enhances data processing and transfer efficiency by decreasing the number of calls between the system’s components. When converting a stream to a byte array with CopyTo(), though, the copy already reads in large blocks, so an extra buffer does not reduce the overall time for reading from the stream.
To use the BufferedStream class to convert a stream to a byte array, let’s start by defining a method:
public byte[] UseBufferedStream(Stream stream)
{
byte[] bytes;
using (var bufferedStream = new BufferedStream(stream))
{
using var memoryStream = new MemoryStream();
bufferedStream.CopyTo(memoryStream);
bytes = memoryStream.ToArray();
}
return bytes;
}
First and foremost, we declare a variable to hold the data we will convert. We then create an instance of the BufferedStream class and pass in the original stream as an argument.
Next, we define a MemoryStream object to write the data we read from the BufferedStream object. To transfer the data between the two streams, we use the CopyTo() method on the BufferedStream object and pass in the MemoryStream object as an argument.
This transfer is necessary because a BufferedStream has no method that returns its contents as an array. Therefore, to convert the contents of the BufferedStream to a byte[] array without knowing its length, we must first copy the data to an intermediate buffer, such as a MemoryStream, and then convert it to a byte array.
Finally, we invoke the ToArray() method on the MemoryStream object to retrieve the data as a byte array and return it to the caller.
What’s the Fastest Way to Convert a Stream to Byte Array in C#?
Now that all our methods are ready, we can evaluate their time and memory performance with a benchmark.
We can now proceed to execute our benchmark tests and examine the results on the console:
| Method | Mean | Error | StdDev | Allocated | |----------------------- |-----------:|---------:|---------:|-----------:| | UseReadExactly | 350.3 us | 10.69 us | 30.84 us | 718.14 KB | | UseBinaryReader | 364.2 us | 9.59 us | 27.35 us | 718.2 KB | | UseBufferedStream | 433.3 us | 8.93 us | 26.34 us | 2639.37 KB | | UseCopyTo | 439.2 us | 15.40 us | 45.41 us | 2639.44 KB | | UseMemoryStream | 448.5 us | 13.57 us | 40.01 us | 2639.42 KB | | UseStreamReader | 934.8 us | 28.22 us | 82.76 us | 3605.96 KB | | UseStreamDotReadMethod | 1,567.8 us | 30.88 us | 30.33 us | 3847.81 KB |
The numbers come from the BenchmarkDotNet library, and the benchmark implementation sits in our repository.
Based on the benchmark test results, we can see that UseReadExactly() is the fastest method to convert a stream to a byte[] array in C#, and it allocates the least memory: one array the size of the payload and nothing else.
UseBinaryReader() comes second, about 4% behind, and allocates the same single array. It stays the method to reach for when the code is already reading typed binary values out of the stream, because the reader is there anyway.
Next come UseBufferedStream(), UseCopyTo() and UseMemoryStream(), and they are one group rather than three places: their error margins overlap, and all three allocate about 2,639 KB, roughly 3.7 times the two methods above them. That extra is the MemoryStream doubling its buffer until it holds the payload, before ToArray() copies it out again.
UseStreamReader() takes 934.8 us, about 2.7 times the winner, and allocates 3,606 KB, the second-highest figure in the table. That is what decoding every byte to text and encoding it again costs.
Finally, the slowest method for this benchmark test is UseStreamDotReadMethod(). It takes about 4.5 times as long as the winner and allocates the most memory of all seven, which is the price of growing a List<byte> 32 bytes at a time.
Speed is only one axis, though, and the one that usually decides the question is whether the stream knows its own length:
| Method | Needs a known Length | Use it when | Watch out for |
|---|---|---|---|
Stream.ReadExactly() | Yes | The stream is seekable and you want one allocation | Throws EndOfStreamException if the stream ends early |
Stream.CopyTo() into MemoryStream | No | The length is unknown: network, pipe, compressed | Holds the payload twice, once in the stream and once in the array |
BinaryReader.ReadBytes() | Yes | You are already reading typed binary values | The (int) cast caps the stream at about 2 GB |
StreamReader plus Encoding.GetBytes() | No | Never, for bytes | Decodes to text and re-encodes, which corrupts anything that is not UTF-8 text without a byte order mark |
BufferedStream wrapping the source | No | The source does many small unbuffered reads | Adds a buffer on top of a CopyTo() that already buffers |
File.ReadAllBytes() | n/a | The source is a file path, not a stream | Not a stream method: see the file-to-byte-array article |
Overall, UseReadExactly() is the most optimal choice to convert a stream to a byte[] array in C#, given its low time and memory requirements. And when the point of the array is to be logged, printed or pasted somewhere, turning those bytes into something a person can read is the next step.
How Do We Convert a Byte Array Back to a Stream in C#?
Going the other way is cheaper, because a byte array already is the data a stream hands out. Wrapping one in a MemoryStream copies nothing: the stream reads straight out of the array we pass in.
Two constructors matter. new MemoryStream(bytes) wraps the array in a fixed size, non resizable stream, so it cannot grow past the array it was given and writing beyond the end fails. new MemoryStream() followed by writes creates an expandable stream that owns its own buffer, which is what we want when more data is still coming.
One detail catches people out. A stream we have just written to sits at the end of its data, so whatever reads it next sees nothing at all. Setting Position back to zero before handing the stream on is the fix, and forgetting it is the usual cause of an empty result.
Let’s wrap an array we already have:
public Stream UseMemoryStreamConstructor(byte[] bytes)
{
return new MemoryStream(bytes);
}
Because that stream reads straight out of our array, writes go the other way too: writing inside its bounds changes the caller’s array in place, and writing past the end throws NotSupportedException with the message “Memory stream is not expandable.”.
When more data is still coming, we want the expandable form instead:
public Stream UseWritableMemoryStream(byte[] bytes)
{
var stream = new MemoryStream();
stream.Write(bytes, 0, bytes.Length);
stream.Position = 0;
return stream;
}
Here we write the whole array into a stream that owns its own buffer, then rewind it, and writing that array out again is converting a byte array back to a file.
The class itself has more to it than the two constructors, and how to use MemoryStream in C# covers the rest.
Conclusion
In this article, we discussed seven methods that we could use to convert a stream to a byte array in C#, and two ways of going back the other way. Additionally, we performed benchmark tests to assess the speed and memory usage of all the methods, and ReadExactly() came out first on both.
Tested with .NET 10.0.10 and BenchmarkDotNet 0.15.8.
