Skip to main content

C# BinaryReader and BinaryWriter

Introduction

When working with files in C#, you'll sometimes need to deal with binary data rather than text. Binary data includes images, audio files, custom file formats, or any data that isn't meant to be read as plain text. C# provides two specialized classes for binary file operations - BinaryReader and BinaryWriter.

These classes offer efficient ways to read and write primitive data types (integers, strings, booleans, etc.) in their binary form, which can be more space-efficient and faster than text-based formats for certain applications.

Understanding Binary Data

Before diving into the classes, it's helpful to understand what binary data means:

  • Binary data is stored in the computer's native format (0s and 1s)
  • It's more compact than text representation
  • It preserves the exact form of data (no conversion issues)
  • It's not human-readable without interpretation

BinaryWriter Class

The BinaryWriter class allows you to write primitive data types as binary values to a stream (typically a file).

Basic Usage of BinaryWriter

csharp
using System;
using System.IO;

class Program
{
static void Main()
{
// Create a file to write to
string filePath = "data.bin";

// Create a BinaryWriter
using (FileStream fileStream = new FileStream(filePath, FileMode.Create))
using (BinaryWriter writer = new BinaryWriter(fileStream))
{
// Write different data types to the file
writer.Write(42); // Write an integer
writer.Write(3.14159); // Write a double
writer.Write(true); // Write a boolean
writer.Write("Hello"); // Write a string
writer.Write('A'); // Write a character
}

Console.WriteLine("Data written to binary file successfully!");
}
}

The code above creates a binary file named "data.bin" and writes various data types to it. The using statements ensure the resources are properly disposed of after use.

Key Methods in BinaryWriter

  • Write(value): Overloaded to write various data types
  • Seek(offset, origin): Changes the current position in the stream
  • Flush(): Clears all buffers and causes any buffered data to be written
  • Close(): Closes the writer and the underlying stream

BinaryReader Class

The BinaryReader class lets you read primitive data types from a binary stream. It's the counterpart to BinaryWriter and is used to read data that was previously written in binary format.

Basic Usage of BinaryReader

csharp
using System;
using System.IO;

class Program
{
static void Main()
{
string filePath = "data.bin";

// Create a BinaryReader
using (FileStream fileStream = new FileStream(filePath, FileMode.Open))
using (BinaryReader reader = new BinaryReader(fileStream))
{
// Read different data types from the file
int intValue = reader.ReadInt32();
double doubleValue = reader.ReadDouble();
bool boolValue = reader.ReadBoolean();
string stringValue = reader.ReadString();
char charValue = reader.ReadChar();

// Display the values
Console.WriteLine($"Integer: {intValue}");
Console.WriteLine($"Double: {doubleValue}");
Console.WriteLine($"Boolean: {boolValue}");
Console.WriteLine($"String: {stringValue}");
Console.WriteLine($"Character: {charValue}");
}
}
}

Output:

Integer: 42
Double: 3.14159
Boolean: True
String: Hello
Character: A

Key Methods in BinaryReader

  • ReadInt32(), ReadDouble(), ReadBoolean(), etc.: Methods to read specific data types
  • ReadBytes(count): Reads a specified number of bytes from the stream
  • PeekChar(): Returns the next available character without advancing the position
  • Close(): Closes the reader and the underlying stream

Important Considerations

  1. Binary Format Compatibility: Binary files created on one system might not be compatible with another due to differences in byte ordering (endianness) or size of data types.

  2. Order Matters: You must read data in exactly the same order and type as it was written.

  3. Error Handling: Always implement error handling for file operations:

csharp
try
{
using (FileStream fs = new FileStream("data.bin", FileMode.Open))
using (BinaryReader reader = new BinaryReader(fs))
{
// Reading operations
}
}
catch (FileNotFoundException)
{
Console.WriteLine("The file could not be found.");
}
catch (IOException ex)
{
Console.WriteLine($"An I/O error occurred: {ex.Message}");
}

Practical Example: Student Record System

Let's see a more comprehensive example of using these classes to build a simple student record system:

csharp
using System;
using System.IO;
using System.Collections.Generic;

class Student
{
public int Id { get; set; }
public string Name { get; set; }
public double GPA { get; set; }
public bool IsActive { get; set; }

public override string ToString() => $"ID: {Id}, Name: {Name}, GPA: {GPA}, Active: {IsActive}";
}

class Program
{
const string FILE_PATH = "students.dat";

static void Main()
{
List<Student> students = new List<Student>
{
new Student { Id = 1, Name = "Alice", GPA = 3.8, IsActive = true },
new Student { Id = 2, Name = "Bob", GPA = 3.2, IsActive = true },
new Student { Id = 3, Name = "Charlie", GPA = 3.5, IsActive = false }
};

// Save students to binary file
SaveStudents(students);

// Read students from binary file
List<Student> loadedStudents = LoadStudents();

// Display loaded students
Console.WriteLine("Loaded Students:");
foreach (var student in loadedStudents)
{
Console.WriteLine(student);
}
}

static void SaveStudents(List<Student> students)
{
using (FileStream fs = new FileStream(FILE_PATH, FileMode.Create))
using (BinaryWriter writer = new BinaryWriter(fs))
{
// Write the count of students first
writer.Write(students.Count);

// Write each student's data
foreach (var student in students)
{
writer.Write(student.Id);
writer.Write(student.Name);
writer.Write(student.GPA);
writer.Write(student.IsActive);
}
}
Console.WriteLine($"{students.Count} students saved to {FILE_PATH}");
}

static List<Student> LoadStudents()
{
List<Student> students = new List<Student>();

using (FileStream fs = new FileStream(FILE_PATH, FileMode.Open))
using (BinaryReader reader = new BinaryReader(fs))
{
// Read the count of students
int count = reader.ReadInt32();

// Read each student's data
for (int i = 0; i < count; i++)
{
Student student = new Student
{
Id = reader.ReadInt32(),
Name = reader.ReadString(),
GPA = reader.ReadDouble(),
IsActive = reader.ReadBoolean()
};

students.Add(student);
}
}

return students;
}
}

Output:

3 students saved to students.dat
Loaded Students:
ID: 1, Name: Alice, GPA: 3.8, Active: True
ID: 2, Name: Bob, GPA: 3.2, Active: True
ID: 3, Name: Charlie, GPA: 3.5, Active: False

This example demonstrates a complete application that stores and retrieves structured data using binary I/O. The student records are efficiently stored in their binary representation and then read back exactly as they were written.

Performance Comparison: Binary vs Text I/O

Binary I/O is generally faster and more space-efficient than text I/O for large amounts of data. Let's compare:

csharp
using System;
using System.IO;
using System.Diagnostics;

class Program
{
static void Main()
{
const int COUNT = 1000000;
Stopwatch stopwatch = new Stopwatch();

// Test binary writing
stopwatch.Start();
using (BinaryWriter writer = new BinaryWriter(File.Open("numbers.bin", FileMode.Create)))
{
for (int i = 0; i < COUNT; i++)
{
writer.Write(i);
}
}
stopwatch.Stop();
Console.WriteLine($"Binary write time: {stopwatch.ElapsedMilliseconds}ms");
Console.WriteLine($"Binary file size: {new FileInfo("numbers.bin").Length} bytes");

// Test text writing
stopwatch.Restart();
using (StreamWriter writer = new StreamWriter(File.Open("numbers.txt", FileMode.Create)))
{
for (int i = 0; i < COUNT; i++)
{
writer.WriteLine(i);
}
}
stopwatch.Stop();
Console.WriteLine($"Text write time: {stopwatch.ElapsedMilliseconds}ms");
Console.WriteLine($"Text file size: {new FileInfo("numbers.txt").Length} bytes");
}
}

Typical output will show that binary I/O is faster and produces smaller files for numeric data.

Common Use Cases for Binary I/O

  1. Custom File Formats: Creating proprietary file formats for your application
  2. Game Development: Saving game states, level data, or character information
  3. Media Processing: Working with image, audio, or video data
  4. High-Performance Data Storage: When speed and space efficiency are critical
  5. Network Communication: Sending compact data over network connections

Summary

BinaryReader and BinaryWriter are powerful tools in C# for dealing with binary data:

  • They allow you to read and write primitive data types in their binary form
  • Binary I/O is faster and more space-efficient than text I/O
  • The order of reading must match the order of writing
  • They're ideal for custom file formats and performance-critical applications

With these classes, you can create efficient file I/O operations that preserve the exact structure of your data.

Exercises

  1. Create a program that writes an array of 10 integers, 5 doubles, and 3 strings to a binary file, then reads them back and displays them.

  2. Implement a simple address book application that stores contact information (name, phone, email) in binary format.

  3. Create a file encryption program that reads a text file, encrypts its contents using a simple algorithm (like XOR with a key), and writes the result as binary data.

  4. Modify the student record system to include additional fields and implement search functionality.

  5. Benchmark the performance difference between binary and text I/O for different data types and file sizes.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)