用于具有私有属性的对象的二进制序列化





5.00/5 (4投票s)
本文旨在展示如何在 C# 中使用 .NET 7 对对象数组进行二进制序列化和反序列化。
引言
BinaryFormatter
类现在被认为是过时的,不安全的,因此不应再使用它(使用 BinaryFormatter 和相关类型的反序列化风险 - 2022/11/28)。
为了对具有私有属性的对象进行二进制序列化,我们可以使用 BinaryWriter
和 BinaryReader
类。
这些类需要程序员为写入和读取磁盘数据的每个单独操作编写代码。
本文旨在展示如何在 C# 中使用 .NET7 对对象数组进行二进制序列化和反序列化。
我们将为 BinaryWriter
和 BinaryReader
类编写一些扩展方法,以便于从磁盘写入和读取对象。
扩展方法是一种向框架的现有类添加新功能的方法,而无需重新编译其源代码。
我们将为 BinaryWriter
和 BinaryReader
类编写的扩展方法应包含在 static
类中。
文章正文
我们的示例的类图如上图所示。
School
类包含一个 Persons
数组,这些人可能拥有 Auto
。
School
类具有 SaveData()
和 LoadData()
方法;它们分别用于序列化和反序列化 persons 数组。
该数组以半动态的方式管理:它具有固定的相当大的维度,并且部分填充了对象。
nPeople
属性表示数组中实际存在的对象数量,同时它也指示数组中的第一个空闲位置。
实际上,nPeople
被加载到从 0
到 nPeople-1
的单元格中。
所有属性都是 private
,因此我们需要一些访问器方法来读取和设置它们的值。
为 XML 和 JSON 序列化提供的类仅对类的公共属性起作用,因此我们选择二进制序列化并使用 BinaryWriter
和 BinaryReader
类。
BinaryWriter
具有 Write()
方法,该方法仅允许将原始数据写入磁盘,例如 string
、char
、整数、双精度浮点数和布尔值。
我们编写了一些扩展方法,以便编写 Auto
和 Person
类型的对象
// extension methods for serialization
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) // check the presence of an auto
{
bw.Write(true); // there is an auto
bw.WriteAuto(a);
}
else
{
bw.Write(false); // no auto
}
}
值得注意的是,由于不能保证所有人都有汽车,因此有必要检查是否存在汽车,并将一个布尔值写入磁盘,以声明它的存在或不存在。
BinaryReader
仅具有诸如 ReadString()
、ReadChar()
、ReadInt32()
、ReadDouble()
、ReadBoolean()
之类的方法,用于读取原始类型的数据。
用于读取 Auto
和 Person
类型对象的扩展方法如下
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);
}
}
以上方法放置在一些 static
类中。
在 School
类中,存在旨在序列化和反序列化 persons 数组及其所有内容的方法
public void SaveData()
{
// serialization
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()
{
// deserialization
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();
}
值得注意的是,除了裸数组之外,还需要序列化 nPeople
的值,因为它给出了数组中放置的真实人数。
Program
类的 Main()
方法允许我们测试这些操作
// some objects for the test
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);
// creation of the school and filling
School school = new School();
school.AddPerson(p1);
school.AddPerson(p2);
school.AddPerson(p3);
// saving to disk
school.SaveData();
// resetting the school and loading data
school = new School();
school.LoadData();
// verification
int n = school.GetNPeople();
for (int i = 0; i < n; i++)
{
Person p = school.GetPerson(i);
Console.WriteLine(p);
}
我们得到以下输出
gianni 20 opel mokka
luca 30 mercedes gla
piero 40
该项目的完整代码已附加到本文中,可以下载。
结论和关注点
在本文中,我们展示了一种简单的方法来替换 BinaryFormatter
类的功能,该类的使用现在是被禁止的。
我们只需要为每个需要序列化的类添加几个扩展方法。
通过这些方法,即使是复杂的对象也可以轻松序列化。
历史
- 2023 年 2 月 5 日:初始版本