ZetCode

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.

Program.cs
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.

Program.cs
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.

Program.cs
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.

Program.cs
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.

Program.cs
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.

Program.cs
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.

Program.cs
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.

Program.cs
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

C# BinaryReader - reference

In this article, we have explored reading binary data in C# using BinaryReader.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all C# tutorials.