演练:使用 C 持久保存对象#

可以使用二进制序列化在实例之间保留对象的数据,这样就可以存储值并在下次实例化对象时检索这些值。

本演练将创建一个基础的 Loan 对象,并将其值保留在文件中。 当重新创建该对象时,将检索文件中的数据。

重要

如果此文件尚不存在,本示例将新建文件。 如果应用程序必须创建文件,则该应用程序必须对文件夹具有 Create 权限。 可使用访问控制列表设置权限。 如果文件已存在,则该应用程序只需要 Write 权限(这是较弱的权限)。 如有可能,较安全的做法是在部署过程中创建文件并仅向单个文件授予 Read 权限(而不是授予文件夹的“创建”权限)。 此外,较安全的做法是将数据写入用户文件夹,而不是根文件夹或 Program Files 文件夹。

重要

此示例将数据存储为二进制格式的文件。 不应将这些格式用于敏感数据,如密码或信用卡信息。

警告

二进制序列化可能会十分危险。 有关详细信息,请参阅 BinaryFormatter 安全指南

先决条件

  • 若要生成并运行,请安装 .NET SDK

  • 安装常用的代码编辑器(如果尚未安装)。

提示

需要安装代码编辑器? 试用 Visual Studio

可在 .NET 示例 GitHub 存储库在线检查示例代码。

创建贷款对象

第一步是创建 Loan 类和使用该类的控制台应用程序:

  1. 创建一个新的应用程序。 键入 dotnet new console -o serialization,在名为 serialization 的子目录下创建新的控制台应用程序。
  2. 在编辑器中打开应用程序,然后添加名为 Loan.cs 的新类。
  3. 将以下代码添加到 Loan 类:
public class Loan : INotifyPropertyChanged
 {
     public double LoanAmount { get; set; }
     public double InterestRatePercent { get; set; }

     [field:NonSerialized()]
     public DateTime TimeLastLoaded { get; set; }

     public int Term { get; set; }

     private string customer;
     public string Customer
     {
         get { return customer; }
         set
         {
             customer = value;
             PropertyChanged?.Invoke(this,
               new PropertyChangedEventArgs(nameof(Customer)));
         }
     }

     [field: NonSerialized()]
     public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

     public Loan(double loanAmount,
                 double interestRate,
                 int term,
                 string customer)
     {
         this.LoanAmount = loanAmount;
         this.InterestRatePercent = interestRate;
         this.Term = term;
         this.customer = customer;
     }
 }

还必须创建使用 Loan 类的应用程序。

串行化 Loan 对象

  1. 打开 Program.cs。 添加以下代码:
Loan TestLoan = new Loan(10000.0, 7.5, 36, "Neil Black");

添加 PropertyChanged 事件的事件处理程序和几行以修改 Loan 对象并显示此更改。 你可以在下列代码中查看添加项:

TestLoan.PropertyChanged += (_, __) => Console.WriteLine($"New customer value: {TestLoan.Customer}");

TestLoan.Customer = "Henry Clay";
Console.WriteLine(TestLoan.InterestRatePercent);
TestLoan.InterestRatePercent = 7.1;
Console.WriteLine(TestLoan.InterestRatePercent);

现在,可运行该代码,并查看当前输出:

New customer value: Henry Clay
7.5
7.1

重复运行此应用程序始终编写相同值。 每次运行程序时都会创建一个新 Loan 对象。 在现实生活中,利率会定期更改,但不必在每次运行应用程序时都更改利率。 序列化代码表示在应用程序的实例之间保存最新的利率。 在下一步中,你将通过向 类添加序列化来 Loan 执行此操作。

使用序列化保持对象

若要使用二进制序列化保留 Loan 类的值,必须先使用 Serializable 特性标记类。 在类定义上方 Loan 添加以下代码:

[Serializable()]

告知 SerializableAttribute 编译器,类中的所有内容都可以使用二进制序列化保存到文件中。 因为 PropertyChanged 事件不表示应该存储的对象图的部分,所以它不应序列化。 执行此操作可能将所有附加到该事件的对象序列化。 可将 NonSerializedAttribute 添加到 PropertyChanged 事件处理程序的字段声明。

[field: NonSerialized()]
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

可使用 field 目标值将特性附加到自动实现的属性的支持字段。 以下代码添加 TimeLastLoaded 属性并将其标记为不可序列化:

[field:NonSerialized()]
public DateTime TimeLastLoaded { get; set; }

下一步是向 LoanApp 应用程序添加序列化代码。 为了将该类序列化并将其写入到文件,可使用 System.IOSystem.Runtime.Serialization.Formatters.Binary 命名空间。 为了避免键入完全限定的名称,可以添加对必要命名空间的引用,如以下代码所示:

using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

下一步是添加代码,在创建对象时对文件中的对象进行反序列化。 向序列化数据的文件名的类中添加一个常量,如以下代码所示:

const string FileName = @"../../../SavedLoan.bin";

接下来,在创建 TestLoan 对象的行后添加以下代码:

if (File.Exists(FileName))
{
    Console.WriteLine("Reading saved file");
    Stream openFileStream = File.OpenRead(FileName);
    BinaryFormatter deserializer = new BinaryFormatter();
    TestLoan = (Loan)deserializer.Deserialize(openFileStream);
    TestLoan.TimeLastLoaded = DateTime.Now;
    openFileStream.Close();
}

首先必须检查该文件是否存在。 如果存在,则创建 Stream 类来读取二进制文件和 BinaryFormatter 类,以转换该文件。 还需将流类型转换为 Loan 对象类型。

然后必须添加代码以将该类序列化到文件中。 以 Main 方式在现有代码后添加以下代码:

Stream SaveFileStream = File.Create(FileName);
BinaryFormatter serializer = new BinaryFormatter();
serializer.Serialize(SaveFileStream, TestLoan);
SaveFileStream.Close();

此时可再次生成并运行应用程序。 首次运行时,请注意起始利率为 7.5,然后更改为 7.1. 关闭该应用程序,然后重新运行。 现在,应用程序打印已读取所保存文件的消息,即使在代码更改它之前,利率也是 7.1。

请参阅