C# BinaryReader
last modified April 20, 2025
This tutorial explores the C# BinaryReader class for reading binary data from files or streams. It covers various scenarios, from basic data reading to advanced techniques like handling structs and custom encodings.
The BinaryReader class, part of the System.IO namespace, reads primitive data types (e.g., int, double, string) from binary streams in a specific encoding. It works seamlessly with BinaryWriter for writing binary data.
BinaryReader is ideal for processing binary files, such as configuration files, serialized objects, or custom data formats. This tutorial demonstrates its use through practical examples that highlight different reading techniques.
Basic BinaryReader Example
This example shows how to read primitive data types from a binary file created with BinaryWriter, demonstrating the basic functionality of BinaryReader.
using System; using System.IO; class Program { static void Main() { // Write data first using (var writer = new BinaryWriter(File.Open("data.bin", FileMode.Create))) { writer.Write(42); writer.Write(3.14); writer.Write(true); writer.Write("Hello Binary"); } // Read data back using (var reader = new BinaryReader(File.Open("data.bin", FileMode.Open))) { int number = reader.ReadInt32(); double pi = reader.ReadDouble(); bool flag = reader.ReadBoolean(); string text = reader.ReadString(); Console.WriteLine($"Int: {number}"); Console.WriteLine($"Double: {pi}"); Console.WriteLine($"Boolean: {flag}"); Console.WriteLine($"String: {text}"); } } }
The program uses BinaryWriter to write an integer, double, boolean, and string to a binary file. BinaryReader then reads these values using type-specific methods like ReadInt32, ReadDouble, ReadBoolean, and ReadString.
The reading order must match the writing order to correctly interpret the binary data. The using statements ensure proper resource management by automatically closing the file streams. This example is ideal for understanding the core functionality of BinaryReader for simple binary file operations.
Reading Arrays from Binary File
This example demonstrates reading an array of integers from a binary file, showing how to handle collections of primitive types with BinaryReader.
using System; using System.IO; class Program { static void Main() { // Write array first int[] numbers = { 1, 2, 3, 4, 5 }; using (var writer = new BinaryWriter(File.Open("array.bin", FileMode.Create))) { writer.Write(numbers.Length); foreach (var num in numbers) { writer.Write(num); } } // Read array back using (var reader = new BinaryReader(File.Open("array.bin", FileMode.Open))) { int length = reader.ReadInt32(); int[] readNumbers = new int[length]; for (int i = 0; i < length; i++) { readNumbers[i] = reader.ReadInt32(); } Console.WriteLine("Read array: " + string.Join(", ", readNumbers)); } } }
The BinaryWriter writes the array length followed by each integer element. This metadata (length) is crucial for BinaryReader to allocate the correct array size when reading. The reader first retrieves the length with ReadInt32, then iterates to read each integer.
This approach ensures accurate reconstruction of the array. It's useful for handling collections in binary files, such as saving lists of scores or coordinates. The example highlights the importance of including metadata to describe the structure of the data.
Reading Mixed Data Types
This example shows how to read a binary file containing mixed data types, demonstrating BinaryReader's ability to handle structured data.
using System; using System.IO; class Program { static void Main() { // Write mixed data using (var writer = new BinaryWriter(File.Open("mixed.bin", FileMode.Create))) { writer.Write("Product"); writer.Write(12345); writer.Write(19.99m); writer.Write(DateTime.Now.Ticks); } // Read mixed data using (var reader = new BinaryReader(File.Open("mixed.bin", FileMode.Open))) { string name = reader.ReadString(); int id = reader.ReadInt32(); decimal price = reader.ReadDecimal(); DateTime date = new DateTime(reader.ReadInt64()); Console.WriteLine($"Name: {name}"); Console.WriteLine($"ID: {id}"); Console.WriteLine($"Price: {price:C}"); Console.WriteLine($"Date: {date:g}"); } } }
The BinaryWriter writes a string, integer, decimal, and DateTime
(as ticks) in sequence. BinaryReader
reads these using ReadString
,
ReadInt32
, ReadDecimal
, and ReadInt64
,
respectively. The DateTime
is reconstructed from its ticks for
precise recovery.
Maintaining the exact order and type of reads is critical to avoid data misinterpretation. This example is suitable for applications reading structured binary files, such as product catalogs or log entries, where multiple data types are combined in a single file.
Handling End of File
This example demonstrates how to read binary data until the end of the file using the BaseStream property to avoid exceptions.
using System; using System.IO; class Program { static void Main() { // Write unknown number of items using (var writer = new BinaryWriter(File.Open("items.bin", FileMode.Create))) { Random rand = new Random(); int count = rand.Next(5, 15); for (int i = 0; i < count; i++) { writer.Write(rand.Next(100)); } } // Read until EOF using (var reader = new BinaryReader(File.Open("items.bin", FileMode.Open))) { Console.WriteLine("Reading numbers:"); while (reader.BaseStream.Position < reader.BaseStream.Length) { int num = reader.ReadInt32(); Console.Write(num + " "); } Console.WriteLine(); } } }
The BinaryWriter
writes a random number of integers to the file.
BinaryReader
uses a while loop to read integers as long as the
stream's current position (BaseStream.Position
) is less than its
total length (BaseStream.Length).
This method prevents reading past the end of the file, which would throw an exception. It's useful for processing binary files with an unknown number of elements, such as log files or data streams where the total size isn't predefined.
Reading Strings with Encoding
This example shows how to read strings with different encodings using BinaryReader, handling both default and custom encoding formats.
using System; using System.IO; using System.Text; class Program { static void Main() { // Write strings with different encodings using (var writer = new BinaryWriter(File.Open("strings.bin", FileMode.Create))) { writer.Write("ASCII text"); writer.Write(Encoding.UTF32.GetBytes("UTF-32 text")); } // Read with specific encodings using (var reader = new BinaryReader(File.Open("strings.bin", FileMode.Open))) { string asciiText = reader.ReadString(); Console.WriteLine("ASCII: " + asciiText); byte[] utf32Bytes = reader.ReadBytes(44); // 11 chars * 4 bytes string utf32Text = Encoding.UTF32.GetString(utf32Bytes); Console.WriteLine("UTF-32: " + utf32Text); } } }
BinaryWriter
writes a UTF-8 string (default for Write(string)) and
a UTF-32 string as raw bytes. BinaryReader
reads the UTF-8 string
with ReadString and the UTF-32 string by reading 44 bytes (11 characters × 4
bytes) and converting them with Encoding.UTF32.GetString
.
This approach is essential for handling binary files with non-standard encodings, such as those produced by legacy systems or specific protocols. It demonstrates BinaryReader's flexibility in reading strings when the default UTF-8 encoding isn't used.
Reading Structs from Binary File
This advanced example shows how to read structured binary data into a C# struct using unsafe code and memory marshaling for precise data mapping.
using System; using System.IO; using System.Runtime.InteropServices; [StructLayout(LayoutKind.Sequential, Pack = 1)] struct Product { public int Id; public float Price; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)] public string Name; } class Program { static unsafe void Main() { // Write sample product using (var writer = new BinaryWriter(File.Open("product.bin", FileMode.Create))) { writer.Write(123); // ID writer.Write(19.99f); // Price writer.Write("Premium Coffee".PadRight(20, '\0')); // Name } // Read into struct using (var reader = new BinaryReader(File.Open("product.bin", FileMode.Open))) { byte[] buffer = reader.ReadBytes(Marshal.SizeOf(typeof(Product))); fixed (byte* ptr = buffer) { Product product = Marshal.PtrToStructure<Product>((IntPtr)ptr); Console.WriteLine($"ID: {product.Id}"); Console.WriteLine($"Price: {product.Price:C}"); Console.WriteLine($"Name: {product.Name.Trim('\0')}"); } } } }
The Product struct uses StructLayout with LayoutKind.Sequential and Pack=1 to ensure fields align exactly with the binary file's format. BinaryWriter writes the struct's data (ID, price, and fixed-length name). BinaryReader reads the exact byte size of the struct, determined by Marshal.SizeOf.
The bytes are marshaled into a Product instance using Marshal.PtrToStructure, with unsafe code to pin the byte array. This technique is powerful for reading complex binary formats, such as those in legacy systems or custom protocols, where data is tightly packed without padding.
Reading Binary Data from MemoryStream
This example demonstrates using BinaryReader to read binary data from a MemoryStream, showing its versatility with non-file streams.
using System; using System.IO; class Program { static void Main() { // Create binary data in memory byte[] data = new byte[16]; new Random().NextBytes(data); // Read from MemoryStream using (var stream = new MemoryStream(data)) using (var reader = new BinaryReader(stream)) { Console.WriteLine("First int: " + reader.ReadInt32()); Console.WriteLine("Second int: " + reader.ReadInt32()); Console.WriteLine("Remaining bytes: " + BitConverter.ToString(reader.ReadBytes(8))); } } }
A MemoryStream
is initialized with a 16-byte array of random data.
BinaryReader
reads this stream as if it were a file, extracting two
32-bit integers with ReadInt32
and the remaining 8 bytes with
ReadBytes
. The bytes are formatted as a hex string using
BitConverter.ToString
for display.
This approach is useful for processing in-memory binary data, such as network packets, serialized objects, or data from APIs. BinaryReader's ability to work with any Stream makes it versatile for scenarios beyond file I/O, enabling consistent binary data handling across different sources.
Error Handling with BinaryReader
This example shows how to handle errors when reading binary data, ensuring robust file processing with try-catch blocks and stream validation.
using System; using System.IO; class Program { static void Main() { // Write partial data using (var writer = new BinaryWriter(File.Open("partial.bin", FileMode.Create))) { writer.Write(42); writer.Write("Incomplete"); // Intentionally omit a double } // Read with error handling using (var reader = new BinaryReader(File.Open("partial.bin", FileMode.Open))) { try { int number = reader.ReadInt32(); string text = reader.ReadString(); double value; // Check if enough bytes remain for a double if (reader.BaseStream.Length - reader.BaseStream.Position >= 8) { value = reader.ReadDouble(); Console.WriteLine($"Double: {value}"); } else { Console.WriteLine("Incomplete data: missing double value"); } Console.WriteLine($"Int: {number}"); Console.WriteLine($"String: {text}"); } catch (EndOfStreamException ex) { Console.WriteLine($"Error: Reached end of file unexpectedly. {ex.Message}"); } catch (IOException ex) { Console.WriteLine($"IO Error: {ex.Message}"); } } } }
The BinaryWriter
writes an integer and a string but omits a double
to simulate incomplete data. BinaryReader attempts to read an integer, string,
and double, using a try-catch block to handle potential
EndOfStreamException
or IOException
errors.
Before reading the double, the program checks if enough bytes remain in the stream, preventing an exception. This example demonstrates robust error handling for binary file reading, crucial for applications processing potentially corrupted or incomplete files, such as log parsers or data recovery tools.
Source
In this article, we have explored reading binary data in C# using BinaryReader.
Author
List all C# tutorials.