65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2023年2月6日

CPOL

3分钟阅读

viewsIcon

5433

downloadIcon

74

本文旨在展示如何在 C# 中使用 .NET 7 对对象数组进行二进制序列化和反序列化。

引言

BinaryFormatter 类现在被认为是过时的,不安全的,因此不应再使用它(使用 BinaryFormatter 和相关类型的反序列化风险 - 2022/11/28)。

为了对具有私有属性的对象进行二进制序列化,我们可以使用 BinaryWriterBinaryReader 类。

这些类需要程序员为写入和读取磁盘数据的每个单独操作编写代码。

本文旨在展示如何在 C# 中使用 .NET7 对对象数组进行二进制序列化和反序列化。

我们将为 BinaryWriterBinaryReader 类编写一些扩展方法,以便于从磁盘写入和读取对象。

扩展方法是一种向框架的现有类添加新功能的方法,而无需重新编译其源代码。

我们将为 BinaryWriterBinaryReader 类编写的扩展方法应包含在 static 类中。

文章正文

我们的示例的类图如上图所示。

School 类包含一个 Persons 数组,这些人可能拥有 Auto

School 类具有 SaveData()LoadData() 方法;它们分别用于序列化和反序列化 persons 数组。

该数组以半动态的方式管理:它具有固定的相当大的维度,并且部分填充了对象。

nPeople 属性表示数组中实际存在的对象数量,同时它也指示数组中的第一个空闲位置。

实际上,nPeople 被加载到从 0nPeople-1 的单元格中。

所有属性都是 private,因此我们需要一些访问器方法来读取和设置它们的值。

为 XML 和 JSON 序列化提供的类仅对类的公共属性起作用,因此我们选择二进制序列化并使用 BinaryWriterBinaryReader 类。

BinaryWriter 具有 Write() 方法,该方法仅允许将原始数据写入磁盘,例如 stringchar、整数、双精度浮点数和布尔值。

我们编写了一些扩展方法,以便编写 AutoPerson 类型的对象

// 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() 之类的方法,用于读取原始类型的数据。

用于读取 AutoPerson 类型对象的扩展方法如下

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 日:初始版本
© . All rights reserved.