We are going to write some extension methods for the BinaryWriter and BinaryReader classes, in order to allow for easy writing, and reading, the objects from disk.
Introduction
The BinaryFormatter
class is now considered obsolete, not safe and therefore it should not be used anymore (Deserialization risks in use of BinaryFormatter and related types - 28/11/2022).
In order to do binary serialization of objects with private attributes, we can use the BinaryWriter
and BinaryReader
classes.
These classes require the programmer’s work to write any single operation for writing and reading data from disk.
This work is aimed at showing how to do binary serialization and deserialization of an array of objects in C# with .NET7.
We are going to write some extension methods for the BinaryWriter
and BinaryReader
classes, in order to allow for easy writing, and reading, the objects from disk.
The extension methods are a means to add new functionalities to an existing class of the framework, without the need to recompile its source code.
The extension methods that we will write for the BinaryWriter
and BinaryReader
classes should be contained in static
classes.
The Article Body
The class diagram of our example is shown in the above picture.
The School
class contains an array of Persons
, which may have an Auto
.
The School
class has the SaveData()
and LoadData()
methods; they serve for, respectively, the serialization and the deserialization of the array of persons.
The array is managed in a semi-dynamic way: it has a fixed rather big dimension and is partially filled with objects.
The nPeople
attribute represents the number of objects actually present in the array and, at the same time, it indicates the first free position in the array.
Indeed, the nPeople
are loaded in the cells from 0
to nPeople-1
.
All the attributes are private
, therefore we need some accessor methods for reading and setting their values.
The classes provided for XML and JSON serialization act only on the public attributes of the classes, therefore we choose the binary serialization and use the BinaryWriter
and BinaryReader
classes.
The BinaryWriter
has the Write()
method which allows to write to disk only primitive data, such as string
s, char
s, integer numbers, double precision floating point numbers and booleans.
We write some extension methods in order to write objects of type Auto
and Person
:
public static void WriteAuto(this BinaryWriter bw, Auto a)
{
bw.Write(a.GetBrand());
bw.Write(a.GetModel());
}
public static void WritePersona(this BinaryWriter bw, Person p)
{
bw.Write(p.GetName());
bw.Write(p.GetAge());
Auto a = p.GetAge();
if (a != null)
{
bw.Write(true);
bw.WriteAuto(a);
}
else
{
bw.Write(false);
}
}
It is worth noting that, as it is not guaranteed that all people have a car, it is necessary to check the presence of an auto and to write to disk a boolean value that states the presence or the absence of it.
The BinaryReader
has only such methods as ReadString()
, ReadChar()
, ReadInt32()
, ReadDouble()
, ReadBoolean()
for reading data of a primitive type.
The extension methods used for reading objects of type Auto
and Person
are the following:
public static Auto ReadAuto(this BinaryReader br)
{
string brand = br.ReadString();
string model = br.ReadString();
return new Auto(brand, model);
}
public static Person ReadPersona(this BinaryReader br)
{
string name = br.ReadString();
int age = br.ReadInt32();
bool hasAuto = br.ReadBoolean();
if (hasAuto)
{
Auto a = br.ReadAuto();
return new Person(name, age, a);
}
else
{
return new Person(name, age);
}
}
The above methods are placed in some static
classes.
In the School
class, there are the methods aimed at serializing and deserializing the array of persons with all their contents:
public void SaveData()
{
FileStream file = File.Create("data.bin");
BinaryWriter bw = new BinaryWriter(file);
bw.Write(nPeople);
for (int i = 0; i < nPeople; i++)
{
bw.WritePerson(array[i]);
}
file.Close();
}
public void LoadData()
{
FileStream file = File.OpenRead("data.bin");
BinaryReader br = new BinaryReader(file);
nPeople = br.ReadInt32();
for (int i = 0; i < nPeople; i++)
{
array[i] = br.ReadPerson();
}
file.Close();
}
It is worth noting that, in addition to the bare array, it is necessary to serialize the value of nPeople
too, because it gives the real number of people placed in the array.
The Main()
method of the Program
class allows us to test the operations:
Auto a1 = new Auto("opel", "mokka");
Person p1 = new Person("gianni", 20, a1);
Auto a2 = new Auto("mercedes", "gla");
Person p2 = new Person("luca", 30, a2);
Person p3 = new Person("piero", 40);
School school = new School();
school.AddPerson(p1);
school.AddPerson(p2);
school.AddPerson(p3);
school.SaveData();
school = new School();
school.LoadData();
int n = school.GetNPeople();
for (int i = 0; i < n; i++)
{
Person p = school.GetPerson(i);
Console.WriteLine(p);
}
We obtain the following output:
gianni 20 opel mokka
luca 30 mercedes gla
piero 40
The complete code for the project is attached to this article and can be downloaded.
Conclusion and Points of Interest
In this article, we have shown a simple way for replacing the functionalities of the BinaryFormatter
class, whose usage is now forbidden.
We only have to add a couple of extension methods to each class we need to serialize.
By means of these methods, it becomes straightforward to serialize even complex objects.
History
- 5th February, 2023: Initial version