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






3.41/5 (8投票s)
解释了序列化、自定义序列化的必要性以及如何在代码中实现自定义序列化。
摘要
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
序列化对象会触发四个开发人员可以实现的事件:OnSerializing
、OnSerialized
、OnDeserializing
和 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 日:初始发布