What we have in this page?
Binary I/O in the .NET Framework uses the BinaryReader and BinaryWriter classes, which read and write .NET primitive types in binary format. As with the TextReader and TextWriter classes, the binary I/O classes use an underlying Stream object to provide a byte stream. Both BinaryReader and BinaryWriter have a BaseStream property that gives access to the underlying Stream.
The following table lists the methods provided by BinaryWriter.
| Public Methods | |
| Symbol | |
| Name | Description |
| Close | Closes the current BinaryWriter and the underlying stream. |
| Equals | Overloaded. Determines whether two Object instances are equal. (Inherited from Object.) |
| Flush | Clears all buffers for the current writer and causes any buffered data to be written to the underlying device. |
| GetHashCode | Serves as a hash function for a particular type. GetHashCode is suitable for use in hashing algorithms and data structures like a hash table. (Inherited from Object.) |
| GetType | Gets the Type of the current instance. (Inherited from Object.) |
| ReferenceEquals | Determines whether the specified Object instances are the same instance. (Inherited from Object.) |
| Seek | Sets the position within the current stream. |
| ToString | Returns a String that represents the current Object. (Inherited from Object.) |
| Write | Overloaded. Writes a value to the current stream. |
|
Table 17 | |
| Protected Methods | |
| Symbol | |
| Name | Description |
| Dispose | Releases the unmanaged resources used by the BinaryWriter and optionally releases the managed resources. |
| Finalize | Allows an Object to attempt to free resources and perform other cleanup operations before the Object is reclaimed by garbage collection. (Inherited from Object.) |
| MemberwiseClone | Creates a shallow copy of the current Object. (Inherited from Object.) |
| Write7BitEncodedInt | Writes a 32-bit integer in a compressed format. |
|
Table 18 | |
If you look at the Visual Studio .NET documentation, you’ll see that the Write function has no fewer than 18 overloads for you to cope with when writing the various basic types provided by the .NET Framework. Because not all the types provided by .NET are compliant with the Common Language Specification (CLS), you need to be careful when using some of the Write methods if you intend the data to be read from code written in other .NET languages.
The CLS defines types that all .NET languages must support. The signed byte and unsigned integer types are not included in the CLS, so they might not be usable from some .NET languages. The most important of these is Microsoft Visual Basic .NET, which doesn’t support any of the non-CLS-compliant types.
The following table describes the functions provided by BinaryReader.
| Public Methods | |
| Symbol | |
| Name | Description |
| Close | Closes the current reader and the underlying stream. |
| Equals | Overloaded. Determines whether two Object instances are equal. (Inherited from Object.) |
| GetHashCode | Serves as a hash function for a particular type. GetHashCode is suitable for use in hashing algorithms and data structures like a hash table. (Inherited from Object.) |
| GetType | Gets the Type of the current instance. (Inherited from Object.) |
| PeekChar | Returns the next available character and does not advance the byte or character position. |
| Read | Overloaded. Reads characters from the underlying stream and advances the current position of the stream. |
| ReadBoolean | Reads a Boolean value from the current stream and advances the current position of the stream by one byte. |
| ReadByte | Reads the next byte from the current stream and advances the current position of the stream by one byte. |
| ReadBytes | Reads count bytes from the current stream into a byte array and advances the current position by count bytes. |
| ReadChar | Reads the next character from the current stream and advances the current position of the stream in accordance with the Encoding used and the specific character being read from the stream. |
| ReadChars | Reads count characters from the current stream, returns the data in a character array, and advances the current position in accordance with the Encoding used and the specific character being read from the stream. |
| ReadDecimal | Reads a decimal value from the current stream and advances the current position of the stream by sixteen bytes. |
| ReadDouble | Reads an 8-byte floating point value from the current stream and advances the current position of the stream by eight bytes. |
| ReadInt16 | Reads a 2-byte signed integer from the current stream and advances the current position of the stream by two bytes. |
| ReadInt32 | Reads a 4-byte signed integer from the current stream and advances the current position of the stream by four bytes. |
| ReadInt64 | Reads an 8-byte signed integer from the current stream and advances the current position of the stream by eight bytes. |
| ReadSByte | Reads a signed byte from this stream and advances the current position of the stream by one byte. |
| ReadSingle | Reads a 4-byte floating point value from the current stream and advances the current position of the stream by four bytes. |
| ReadString | Reads a string from the current stream. The string is prefixed with the length, encoded as an integer seven bits at a time. |
| ReadUInt16 | Reads a 2-byte unsigned integer from the current stream using little endian encoding and advances the position of the stream by two bytes. |
| ReadUInt32 | Reads a 4-byte unsigned integer from the current stream and advances the position of the stream by four bytes. |
| ReadUInt64 | Reads an 8-byte unsigned integer from the current stream and advances the position of the stream by eight bytes. |
| ReferenceEquals | Determines whether the specified Object instances are the same instance. (Inherited from Object.) |
| ToString | Returns a String that represents the current Object. (Inherited from Object.) |
|
Table 19 | |
| Protected Methods | |
| Symbol | |
| Name | Description |
| Dispose | Releases the unmanaged resources used by the BinaryReader and optionally releases the managed resources. |
| FillBuffer | Fills the internal buffer with the specified number of bytes read from the stream. |
| Finalize | Allows an Object to attempt to free resources and perform other cleanup operations before the Object is reclaimed by garbage collection. (Inherited from Object.) |
| MemberwiseClone | Creates a shallow copy of the current Object. (Inherited from Object.) |
| Read7BitEncodedInt | Reads in a 32-bit integer in a compressed format. |
|
Table 20 | |
| Unlike BinaryWriter, BinaryReader provides separate functions to read each of the basic types. The following exercise that follows shows you how to use the BinaryReader and BinaryWriter classes to write binary data to a file and read it back. It uses a class, Customer, which represents a bank customer who has a name, an account number, and a current balance. The program writes customer details to a file in binary and reads them back.
1. Create a new Visual C++ CLR Console Application project named CppBinRead.
|
2. Add the using declaration for System::IO to the start of the code, like this:
using namespace System::IO;

3. Add a new class definition before the main() function.
// The Customer class
ref class Customer
{
String^ name;
long accNo;
double balance;
public:
// Constructors
Customer() : name(nullptr), accNo(0), balance(0.0) {}
Customer(String^ s, long l, double b) : name(s), accNo(l), balance(b) {}
// Write object data to a BinaryWriter
void Write(BinaryWriter^ bw)
{
bw->Write(name);
bw->Write(accNo);
bw->Write(balance);
}
// Read object data from a BinaryReader
void Read(BinaryReader^ br)
{
name = br->ReadString();
accNo = br->ReadInt32();
balance = br->ReadDouble();
}
// Properties to retrieve the instance variables
property String^ Name
{
String^ get() { return name; }
}
property long Account
{
long get() { return accNo; }
}
property double Balance
{
double get() { return balance; }
}
};
The class has three data members: a String for the name, a long for the account number, and a double for the balance. There are constructors to create default and fully populated objects, and there’s a set of read-only properties to allow access to the data members. The Read and Write functions use BinaryReader and BinaryWriter objects to read and write the state of the object in binary format.
4. Edit the main() function so that it uses the command-line argument parameters, as follows:
// Get the command line arguments
array<String^>^args = Environment::GetCommandLineArgs();

5. Add the following code to main() to check that the user passes in a file name and save the path as a String:
// Check for required arguments
if (args->Length < 2)
{
Console::WriteLine(L"Usage: CppBinRead path");
return -1;
}
// Save the path
String^ path = gcnew String(args[1]);

This code is very similar to the argument-handling code that has been used in other exercises in this module. Note that for simplicity we are not checking the path for validity, but it’s easy and advisable to add such a check in a real application.
6. Create some Customer objects.
// Create or instantiate some customers objects
Customer^ c1 = gcnew Customer(L"Mike Smith", 1234567, 600.0);
Customer^ c2 = gcnew Customer(L"Billy Jones", 2345678, 7000.5);
Customer^ c3 = gcnew Customer(L"Mark Davies", 3456789, 30000.0);

7. To write the objects, you need a BinaryWriter and a FileStream to do the output to the file. Add the following codes.
try
{
// Create a FileStream
FileStream^ fstrm = gcnew FileStream(path, FileMode::Create, FileAccess::ReadWrite);
// Create a BinaryWriter to use the FileStream
BinaryWriter^ binw = gcnew BinaryWriter(fstrm);
}
catch(System::Exception^ pe)
{
Console::WriteLine(pe->ToString());
}

The FileStream will write to a file, creating it if necessary and the file will be opened with read/write access because you’ll be reading from it later in the program. Once again, it’s good practice to put the I/O class creation code in a try block to catch any problems that might occur.
8. Writing the object data to the file is simply a case of calling the Write function, passing in a pointer to the BinaryWriter. Add the following code at the end of the try block:
// Write the customters to the file
c1->Write(binw);
c2->Write(binw);
c3->Write(binw);

9. Because the file was opened with read/write access, you can now read from the file. To do so, create a BinaryReader object and attach it to the same FileStream, as shown here:
// Create a BinaryReader that reads from the same FileStream
BinaryReader^ binr = gcnew BinaryReader(fstrm);
![]() |
10. Before you can read from a file that you’ve written to, you have to move the position of the seek pointer.
// Move back to the beginning
binr->BaseStream->Seek(0, SeekOrigin::Begin);

Notice that this code uses the BaseStream property and its associated seek pointer to get at the underlying Stream object.
Every stream in .NET has a seek pointer associated with it, which represents the position in the stream at which the next read or write operation will take place. This pointer is automatically repositioned when you use Stream class methods to read or write the stream, but it’s also possible to move this pointer yourself if you need to (and if you know what you’re doing).
The most likely time you’ll need to move the pointer is when you open a stream for read/write access. Once you’ve written to the stream, the seek pointer will be positioned at the end, ready for the next write. If you want to read from the stream, you’ll have to reposition the pointer. You reposition the pointer by using the Seek method of the Stream object, giving it an offset in bytes and a position where the offset should be applied. Offsets can be positive or negative, the sign reflecting whether the offset should move toward the start (negative) or end (positive) of the stream. The possible positions are members of the SeekOrigin enumeration, and they can be SeekOrigin::Current (the current position), SeekOrigin::Begin (the start of the Stream), or SeekOrigin::End (the end of the Stream).
11. Create a new Customer, and read its details from the file, as follows:
// Create a new customer, and read details from the file
Customer^ c4 = gcnew Customer();
c4->Read(binr);
Console::WriteLine(L"Balance for {0} (a/c {1}) is {2}",
c4->Name, (Object^)(c4->Account), (Object^)(c4->Balance));

The new Customer object has all its fields set to default values. The call to Read tells it to read its data from the current position in the file. The obvious potential problem is that the Read function will read from wherever the BinaryReader is currently positioned. If it isn’t at the beginning of a Customer object’s data, you can expect to get an exception thrown. If you want to save the state of objects in a real-world program, you wouldn’t do it manually like this. The System::Runtime::Serialization namespace contains classes that help you save and restore the state of objects in an efficient way.
12. Add some informative message to the standard output. The complete source code for this part is given below.
// CppBinRead.cpp : main project file.
#include "stdafx.h"
using namespace System;
using namespace System::IO;
// The Customer class
ref class Customer
{
String^ name;
long accNo;
double balance;
public:
// Constructors
Customer() : name(nullptr), accNo(0), balance(0.0) {}
Customer(String^ s, long l, double b) : name(s), accNo(l), balance(b) {}
// Write object data to a BinaryWriter
void Write(BinaryWriter^ bw)
{
bw->Write(name);
bw->Write(accNo);
bw->Write(balance);
}
// Read object data from a BinaryReader
void Read(BinaryReader^ br)
{
name = br->ReadString();
accNo = br->ReadInt32();
balance = br->ReadDouble();
}
// Properties to retrieve the instance variables
property String^ Name
{
String^ get() { return name; }
}
property long Account
{
long get() { return accNo; }
}
property double Balance
{
double get() { return balance; }
}
};
int main()
{
// Get the command line arguments
array<String^>^args = Environment::GetCommandLineArgs();
// Check for required arguments
if (args->Length < 2)
{
Console::WriteLine(L"Usage: CppBinRead path");
return -1;
}
// Save the path
String^ path = gcnew String(args[1]);
// Create or instantiate some customers objects
Customer^ c1 = gcnew Customer(L"Mike Smith", 1234567, 600.0);
Customer^ c2 = gcnew Customer(L"Billy Jones", 2345678, 7000.5);
Customer^ c3 = gcnew Customer(L"Mark Davies", 3456789, 30000.0);
try
{
// Create a FileStream
Console::WriteLine("Creating a FileStream object");
FileStream^ fstrm = gcnew FileStream(path, FileMode::Create,
FileAccess::ReadWrite);
// Create a BinaryWriter to use the FileStream
Console::WriteLine("Creating a BinaryWriter...");
BinaryWriter^ binw = gcnew BinaryWriter(fstrm);
// Write the customters to the file
Console::WriteLine("Write the customers to the file...");
c1->Write(binw);
c2->Write(binw);
c3->Write(binw);
// Create a BinaryReader that reads from the same FileStream
Console::WriteLine("Creating a BinaryReader that reads from the same FileStream...");
BinaryReader^ binr = gcnew BinaryReader(fstrm);
// Move back to the beginning
Console::WriteLine("Move back to the beginning");
binr->BaseStream->Seek(0, SeekOrigin::Begin);
// Create a new customer, and read details from the file
Console::WriteLine("Creating new customer, and read details from the file:");
Customer^ c4 = gcnew Customer();
c4->Read(binr);
Console::WriteLine(L"\nBalance for {0} (a/c {1}) is {2}",
c4->Name, (Object^)(c4->Account), (Object^)(c4->Balance));
}
catch(System::Exception^ pe)
{
Console::WriteLine(pe->ToString());
}
return 0;
}
13. Build and run the application at command prompt, providing a suitable file name and the following figure is a sample output.

| To | Do this |
| Write text to a file. | Create a StreamWriter that outputs to a FileStream, and then use the Write and WriteLine members of StreamWriter. For example:
Flush and close the StreamWriter when you’re finished with it. For example:
|
| Read text from a file. | Create a StreamReader that reads from a FileStream, and then use the ReadLine member of StreamReader. For example:
|
| Write binary values to a file. | Create a BinaryWriter that outputs to a FileStream, and then use the overloaded Write members of BinaryWriter. For example:
|
| Read binary values from a file. | Create a BinaryReader that reads from a FileStream, and then use the ReadXxx members of BinaryReader. For example:
|
| Find out information about a file. | Use the static functions provided by the File class. If you’re going to perform several operations on the same file, consider creating a FileInfo object and using that instead. |
| Find out information about a directory. | Use the static functions provided by the Directory class. If you’re going to perform several operations on the same file, consider creating a DirectoryInfo object and using that instead. |
|
Table 21 | |