An elegant way to consume (all bytes of a) BinaryReader?
Asked Answered
K

6

78

Is there an elegant to emulate the StreamReader.ReadToEnd method with BinaryReader? Perhaps to put all the bytes into a byte array?

I do this:

read1.ReadBytes((int)read1.BaseStream.Length);

...but there must be a better way.

Kowalski answered 23/12, 2011 at 7:5 Comment(1)
i think after all this is the bet elegant wayGoss
O
111

Original Answer (Read Update Below!)

Simply do:

byte[] allData = read1.ReadBytes(int.MaxValue);

The documentation says that it will read all bytes until the end of the stream is reached.


Update

Although this seems elegant, and the documentation seems to indicate that this would work, the actual implementation (checked in .NET 2, 3.5, and 4) allocates a full-size byte array for the data, which will probably cause an OutOfMemoryException on a 32-bit system.

Therefore, I would say that actually there isn't an elegant way.

Instead, I would recommend the following variation of @iano's answer. This variant doesn't rely on .NET 4:
Create an extension method for BinaryReader (or Stream, the code is the same for either).

public static byte[] ReadAllBytes(this BinaryReader reader)
{
    const int bufferSize = 4096;
    using (var ms = new MemoryStream())
    {
        byte[] buffer = new byte[bufferSize];
        int count;
        while ((count = reader.Read(buffer, 0, buffer.Length)) != 0)
            ms.Write(buffer, 0, count);
        return ms.ToArray();
    }
    
}
Oralla answered 23/12, 2011 at 7:19 Comment(7)
This gives me an OutOfMemoryException in .NET 4.0 (testing with LINQPad). Indeed, decompiling the source with Reflector reveals that ReadBytes tries to allocate a byte array with size of the given count: byte[] buffer = new byte[count];.Code
@Code You are correct. I also decompiled .NET 2.0, and it's the same. I'm gonna update my answer with a disclaimer.Oralla
Can someone explain to me the basics why buffer = new byte[count] would cause an outofmemory exception? I would like to understand the fundamentals of buffering why it's needed. ThanksHeti
@ShrageSmilowitz Well, if you create an array that holds int.MaxValue 32-bit integers, you'll be allocating 8GB of memory ... so that's why you should build the results using smaller buffers!Oralla
The second attempt is bad too, it's not short nor elegant. There are much better answers here.Skyler
@Skyler Back in 2011, when .NET 3.5 was dominant, this was the simplest answer. And I agree with you, iano's answer is much better nowadays.Oralla
Is this actually better than the approach from the question? The line read1.ReadBytes((int)read1.BaseStream.Length); seems not that bad to justify an extension method. As long as Microsoft doesn't add ReadToEnd to BinaryReader (which is present in StreamReader) I'd stick with the one-liner.Granulation
C
82

There is not an easy way to do this with BinaryReader. If you don't know the count you need to read ahead of time, a better bet is to use MemoryStream:

public byte[] ReadAllBytes(Stream stream)
{
    using (var ms = new MemoryStream())
    {
        stream.CopyTo(ms);
        return ms.ToArray();
    }
}

To avoid the additional copy when calling ToArray(), you could instead return the Position and buffer, via GetBuffer().

Code answered 6/4, 2012 at 1:5 Comment(8)
I agree, this is probably the most elegant answer. Worth noting, though, Stream.CopyTo is only available in .NET 4.Oralla
+1. I stumbled across this answer when searching for a solution for my woes. I had a problem with a class in a 3rd party assembly (from which I wanted to get all the bytes) which derived from Stream but its Length property was always zero. I initially tried an extension method-based approach, but felt it was unwieldy.Immoral
I would add stream.Position = 0; before CopyToHash
how do you get stream? Hate these incomplete answersTwocycle
@GustavoBaiocchiCosta yourBinaryReader.BaseStreamProtoplast
Take a look at my just-posted version. Not sure if binaryReader.BaseStream.Length was available back then, but it is now so we can use that without having to construct a new MemoryStream! :)Piapiacenza
This led me to the solution I needed. Note that Stream also has a CopyToAsync methodMoly
Please be aware this will cause some buffers allocation at MemoryStream side. Typically twice the size of the stream (since MemoryStream buffer is doubled each type it reach maximum capacity)Underhill
P
4

To copy the content of a stream to another, I've solved reading "some" bytes until the end of the file is reached:

private const int READ_BUFFER_SIZE = 1024;
using (BinaryReader reader = new BinaryReader(responseStream))
{
    using (BinaryWriter writer = new BinaryWriter(File.Open(localPath, FileMode.Create)))
    {
        int byteRead = 0;
        do
        {
            byte[] buffer = reader.ReadBytes(READ_BUFFER_SIZE);
            byteRead = buffer.Length;
            writer.Write(buffer);
            byteTransfered += byteRead;
        } while (byteRead == READ_BUFFER_SIZE);                        
    }                
}
Preterhuman answered 16/5, 2014 at 13:26 Comment(1)
That worked for me thanks. (You need to remove the 'byteTransfered += byteRead;' line though to run as-is.)Roldan
S
1

Had the same problem.
First, get the file's size using FileInfo.Length.
Next, create a byte array and set its value to BinaryReader.ReadBytes(FileInfo.Length). e.g.

var size = new FileInfo(yourImagePath).Length;
byte[] allBytes = yourReader.ReadBytes(System.Convert.ToInt32(size));
Sulphuryl answered 14/4, 2021 at 9:45 Comment(1)
Won't help if the stream is not a file, though.Xerophilous
R
0

Another approach to this problem is to use C# extension methods:

public static class StreamHelpers
{
   public static byte[] ReadAllBytes(this BinaryReader reader)
   {
      // Pre .Net version 4.0
      const int bufferSize = 4096;
      using (var ms = new MemoryStream())
      {
        byte[] buffer = new byte[bufferSize];
        int count;
        while ((count = reader.Read(buffer, 0, buffer.Length)) != 0)
            ms.Write(buffer, 0, count);
        return ms.ToArray();
      }

      // .Net 4.0 or Newer
      using (var ms = new MemoryStream())
      {
         stream.CopyTo(ms);
         return ms.ToArray();
      }
   }
}

Using this approach will allow for both reusable as well as readable code.

Rapport answered 29/5, 2014 at 2:51 Comment(1)
This does not build - the "stream" variable under the .NET 4.0 section is not defined anywhere.Thevenot
P
0

I use this, which utilizes the underlying BaseStream property to give you the length info you need. It keeps things nice and simple.

Below are three extension methods on BinaryReader:

  • The first reads from wherever the stream's current position is to the end
  • The second reads the entire stream in one go
  • The third utilizes the Range type to specify the subset of data you are interested in.
public static class BinaryReaderExtensions {

    public static byte[] ReadBytesToEnd(this BinaryReader binaryReader) {
    
        var length = binaryReader.BaseStream.Length - binaryReader.BaseStream.Position;
        return binaryReader.ReadBytes((int)length);
    }
    
    public static byte[] ReadAllBytes(this BinaryReader binaryReader) {
    
        binaryReader.BaseStream.Position = 0;
        return binaryReader.ReadBytes((int)binaryReader.BaseStream.Length);
    }

    public static byte[] ReadBytes(this BinaryReader binaryReader, Range range) {

        var (offset, length) = range.GetOffsetAndLength((int)binaryReader.BaseStream.Length);
        binaryReader.BaseStream.Position = offset;
        return binaryReader.ReadBytes(length);      
    }
}

Using them is then trivial and clear...

// 1 - Reads everything in as a byte array
var rawBytes = myBinaryReader.ReadAllBytes();

// 2 - Reads a string, then reads the remaining data as a byte array
var someString = myBinaryReader.ReadString();
var rawBytes = myBinaryReader.ReadBytesToEnd();

// 3 - Uses a range to read the last 44 bytes
var rawBytes = myBinaryReader.ReadBytes(^44..);

Piapiacenza answered 18/1, 2021 at 4:7 Comment(1)
Underlying BaseStream may not support Length and/or Position (e.g. BrotliStream). However it's still better to try this code if (BaseStream.CanSeek).Sldney

© 2022 - 2024 — McMap. All rights reserved.