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

C# .NET 中的序列化 I - 自定义序列化

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.41/5 (8投票s)

2010年7月15日

CPOL

4分钟阅读

viewsIcon

80018

downloadIcon

669

解释了序列化、自定义序列化的必要性以及如何在代码中实现自定义序列化。

摘要

C# .NET 中的序列化在远程处理等各种功能中起着关键作用。开发人员通常需要执行自定义序列化,以便能够完全控制序列化和反序列化过程。因此,标准的 .NET 序列化过程不足以让开发人员控制这些过程。在本系列文章中,Anupam Banerji 将解释序列化、自定义序列化的必要性以及如何在代码中实现自定义序列化。

引言

.NET Framework 中的对象序列化是一项新功能。在序列化之前,存储类对象的唯一方法是将对象写入流对象。这有两个后果。首先,必须编写代码来存储每个对象。如果属性是用户定义的类型,或者是一个包含多个其他对象的对象,那么这个任务会很快变得非常复杂。其次,如果类对象发生更改,则必须同时更改存储它们的代码。这会导致每次更改都要加倍努力。

引入序列化是为了向开发人员提供一种简单、高效且一致的方式来存储类对象。实现标准序列化所需的要求非常少。.NET 标准序列化模型还包括序列化事件,以便在检索已存储对象时重新计算值。

标准序列化

标准序列化是通过一系列属性在类中实现的。要实现类的序列化,请在类声明上方添加 [Serializable] 属性。要排除任何计算字段,请使用 [NonSerialized] 属性对其进行标记。

要重新计算对象在反序列化时,开发人员必须实现 IDeserializationCallback 接口。

要序列化一个类,开发人员可以在 BinaryFormatter 对象和 SoapFormatter 对象之间进行选择。当序列化和反序列化发生在两个 .NET 程序集之间时,应使用 BinaryFormatter 序列化对象。当序列化和反序列化发生在 .NET 程序集和符合 Simple Object Access Protocol (SOAP) 的可执行文件之间时,应使用 SoapFormatter 对象。SOAP 格式将在另一篇文章中讨论。

自定义序列化

自定义序列化是通过 ISerializable 接口实现的。该接口实现 GetObjectData() 方法和一个用于反序列化的重载构造函数。GetObjectData() 方法实现如下:

public void GetObjectData(SerializationInfo info, StreamingContext context)
{
    // Implemented code
}

该方法接受两个参数,一个是实现 IFormatterConverter 接口的 SerializationInfo 对象。我们将在下面的示例中使用它。StreamingContext 对象包含有关序列化对象用途的信息。例如,当序列化对象图发送到远程或未知位置时,会设置 Remoting 类型的 StreamingContext

重载构造函数有两个参数;SerializationInfo 对象和 StreamingContext 对象。

BinaryFormatter 序列化对象会触发四个开发人员可以实现的事件:OnSerializingOnSerializedOnDeserializing OnDeserialized。这些事件被实现为实现 ISerializable 接口的类中的属性。标记为序列化事件的方法必须带有 StreamingContext 参数,否则会发生运行时异常。方法还必须标记为 void

如果同时实现了这两个接口,则 IDe-serializationCallback 接口中的 OnDeserialization() 方法将在 OnDeserialized 事件之后调用,如下面的示例输出所示。

一个快速示例:自定义序列化类

我们在下面的类声明中实现了两个序列化接口。

using System.Runtime.Serialization;

[Serializable]
class TestClass : ISerializable, IDeserializationCallback  
{
    public string Name
    {
        get;
        private set;
    }

    public int ToSquare
    {
        get;
        private set;
    }

    [NonSerialized]
    public int Squared;

    public TestClass(string name, int toSquare)
    {
        Name = name;
        ToSquare = toSquare;
        ComputeSquare();
    }

    public TestClass(SerializationInfo     info, StreamingContext context)
    {
        // Deserialization Constructor 

        Name = info.GetString("Name");
        ToSquare = info.GetInt32("ToSquare");
        Console.WriteLine("Deserializing constructor");
        ComputeSquare();
    }

    private void ComputeSquare()
    {
        Squared = ToSquare * ToSquare;
    }

    [OnSerializing]
    private void OnSerializing(StreamingContext context)
    {
        Console.WriteLine("OnSerializing fired.");
    }

    [OnSerialized]
    private void OnSerialized(StreamingContext context)
    {
        Console.WriteLine("OnSerialized fired.");
    }

    [OnDeserializing]
    private void OnDeserializing(StreamingContext context)
    {
        Console.WriteLine("OnDeserializing fired.");
    }

    [OnDeserialized]
    private void OnDeserialized(StreamingContext context)
    {
        Console.WriteLine("OnDeserialized fired.");
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        Console.WriteLine("Serializing...");
        info.AddValue("Name", Name);
        info.AddValue("ToSquare", ToSquare);
    }
    
    void IDeserializationCallback.OnDeserialization(object sender)
    {
         Console.WriteLine("IDeserializationCallback.OnDeserialization method."); 
            ComputeSquare();
    }
}

Squared 字段未被序列化,而是在 TestClass 对象反序列化时重新计算的。在 (反)序列化过程中触发的四个事件说明了事件对开发人员的潜在用途。此示例中的方法将 (反)序列化过程的进度写入控制台。

我们按如下方式编写调用函数以进行 (反)序列化:

TestClass tc = new TestClass("Test", 3);
FileStream fs = new FileStream("Serialized.txt", FileMode.Create);
BinaryFormatter bf = new BinaryFormatter();

bf.Serialize(fs, tc);
fs.Close();
tc = null;

fs = new FileStream("Serialized.txt", FileMode.Open);

tc = (TestClass)bf.Deserialize(fs);

Console.WriteLine("Squared = " + tc.Squared);

序列化输出被写入 Serialized.txt 文件。这是一个包含 TestClass 实例状态的二进制文件。程序集执行后控制台的输出是:

OnSerializing fired.
Serializing...
OnSerialized fired.
OnDeserializing fired.
Deserializing constructor
OnDeserialized fired.
IDeserializationCallback.OnDeserialization method called.

OnDeserialization() 方法最后被调用,而不是在 OnDeserializing OnDeserialized 事件之间。

通过将 BinaryFormatter 的实例替换为 SoapFormatter 的实例,我们可以实现 SOAP 格式化程序而不是二进制格式化程序。要实例化 SoapFormatter,请确保引用了 System.Runtime.Serialization.Formatters.Soap 命名空间。.NET Framework 3.5 文档指出 SoapFormatter 类已过时。但是,开发人员会遇到该类的实现,尤其是在需要可移植对象图的程序集中。

TestClass tc = new TestClass("Test", 3);
FileStream fs = new FileStream("Serialized.txt", FileMode.Create);
SoapFormatter sf = new SoapFormatter();

sf.Serialize(fs, tc);
fs.Close();
tc = null;

fs = new FileStream("Serialized.txt", FileMode.Open);
tc = (TestClass)sf.Deserialize(fs);
Console.WriteLine("Squared = " + tc.Squared);

SoapFormatter 对象还支持触发四个序列化事件,并且控制台输出与实现 BinaryFormatter 对象时的输出相同。

SoapFormatter 创建的可读格式的序列化对象。Serialization.txt 文件包含以下 XML 输出:

<SOAP-ENV:Envelope …>

<SOAP-ENV:Body>

<a1:TestClass id="ref-1" …>

<Name id="ref-3">Test</Name>

<ToSquare>3</ToSquare>

</a1:TestClass>

</SOAP-ENV:Body>

</SOAP-ENV:Envelope>

序列化对象中的项以蓝色标记。另一种语言或平台中的 XML 读取器可以轻松读取和重建 TestClass 实例。XML 输出是可以控制的。我将在后续文章中介绍这一点。

结论

.NET 中的自定义序列化允许开发人员完全控制 (反)序列化过程。在设计可序列化类之前,应理解事件序列以及实现的接口和属性。序列化格式化程序的选择将决定类设计。如果存在未确定的部署和集成问题,应实现一个为广泛应用程序设计序列化过程的设计。

这是本系列的第一篇文章。

要下载此技术文章的 PDF 格式,请访问 Coactum Solutions 网站:http://www.coactumsolutions.com

历史

  • 2010 年 7 月 15 日:初始发布
© . All rights reserved.