BinaryReader and BinaryWriter fill a hole in Microsoft's Base Class Library by providing an easy to use class to read and write binary streams to and from byte arrays or numeric values in a platform independent manner.
Introduction
Microsoft .NET provides a number of reader and writer classes for reading and writing textual data but nothing to read or write binary data other than Stream
itself. Sure, they provide BitConverter
but it's not very platform neutral and does not manage a cursor. This article and accompanying code aims to fill that gap in the .NET framework.
Update: Now supports float
and double
values
Update 2: Added BinaryWriter
, changed library name
Conceptualizing this Mess
Typically, when reading or writing from or to a binary file, you frequently deal with numeric values which may be several bytes in length and encoded differently than the machine you're running on expects. You'll frequently run into cases where you must reorder the bytes that make up a word
, dword
or qword
, float
or double
. This class makes reading and writing numeric values to or from a stream in any byte order very easy. We can use the BinaryReader
class and its derivatives, BinaryStreamReader
and BinaryCollectionReader
to read values in any byte order we need. Similarly, we can use BinaryWriter
and its derivatives, BinaryStreamWriter
and BinaryCollectionWriter
to write to a Stream
or to a collection of bytes, respectively.
Coding this Mess
Included is a small demo that simply reads a few values from the included MIDI file which I downloaded from one of those ringtone MIDI sites:
using(var br = new BinaryStreamReader(@"..\..\GORILLAZ_-_Feel_Good_Inc.mid"))
{
if("MThd"!=br.ReadFixedString(4,Encoding.ASCII))
throw new ApplicationException("The file is not a MIDI file");
var len = br.ReadInt32BE();
var ba = br.ReadBytes(len);
using (var br2 = new BinaryCollectionReader(ba))
{
Console.WriteLine("MIDI File Type: " + br2.ReadInt16BE().ToString());
Console.WriteLine("MIDI Track Count: " + br2.ReadInt16BE().ToString());
Console.WriteLine("MIDI TimeBase: " + br2.ReadInt16BE().ToString());
}
}
Hopefully, the comments will clarify what it's doing. MIDI files are stored in big-endian format while Intel machines are little-endian. We therefore use ReadXXXXBE()
- note the BE suffix - to read a big-endian value. Read methods sufficed with LE meanwhile, read things in little endian form, while ReadXXXX()
with no suffix reads a value in your platform's native byte order. Using the correct method will ensure that the value is translated to the appropriate byte order for your platform as necessary. One thing to note above is the creation of an additional reader over a byte array. This didn't have to be implemented that way as the comment says, but it illustrates a technique in that it uses a nested reader to safely read a chunk or substream of values. This can be useful in cases where a file is divided into different chunks or substreams, like a MIDI file is. However, the above was so simple we didn't strictly need to do it that way, as I said.
I haven't covered writing in the demo because it would make the demo project a lot more involved to write MIDI files. The writing is the opposite of reading, and the APIs are mirror images of one another, so it should be easy to work out. Everything is documented as well.
This library could use plenty of additional read and write methods, such as read and write methods for decimal
s, zero-terminated string
s, etc. I'm leaving that as an exercise for the reader.
History
- 19th March, 2020 - Initial submission
- 19th March, 2020 - Updated to support float and double values
- 19th March, 2020 - Updated to add BinaryWriter and derivatives. Changed the name of the library