自定义序列化 - 第二部分






3.86/5 (10投票s)
.NET 中的自定义序列化
引言
在上一篇文章中,我讨论了 .NET 如何在 Web 服务场景中进行序列化,将对象转换为 XML,反之亦然。在本文中,我将更详细地讨论 .NET 序列化类以及如何使用它们。
自定义序列化与反序列化
自定义序列化是指我们确定对象应如何转换为字节流的过程。.NET 提供了不同的自定义序列化方法。
使用特性
属性应用于类级别,并标记该类的实例可以被序列化。类的属性、public
字段将根据两个属性进行序列化。它们是:
[XmlAttribute]
:表示public
字段或属性将被序列化为 XML 节点中的一个属性[XmlElement]
:表示public
字段或属性将被序列化为 XML 节点中的一个元素
如果您不希望序列化某个字段,可以将其标记为 [NonSerialized]
。以下代码片段展示了 Customer
类,它被标记为 Serializable
,并且 public
字段被序列化为属性和元素。
[Serializable]
public class Customer
{
[XmlAttribute("ID")]
public string Id;
[XmlElement("Name")]
public string Name;
[XmlElement("Hire")]
public DateTime HireDate;
[XmlElement("Rate")]
public decimal RatePerHour;
[XmlAttribute("Dob")]
public DateTime Dob;
public Customer()
{
}
}
定义实体类后,您可以使用 System.Xml.Serialization namespace
中的 XmlSerializer
类来序列化和反序列化实例。以下代码片段序列化 Customer
实例,将其转换为 string
并在文本框中显示结果。请参考示例应用程序中的 frmCustomer
。
XmlSerializer serializer = new XmlSerializer(typeof(Customer));
StringWriter writer = new StringWriter();
serializer.Serialize(writer, customer);
textBoxResult.Text = writer.ToString();
反序列化是反向过程,代码片段如下。
StringReader reader = new StringReader(textBoxResult.Text);
XmlSerializer serializer = new XmlSerializer(typeof(Customer));
Customer customer = (Customer) serializer.Deserialize(reader);
优点和缺点
这种方法具有以下优点:
- 可以灵活地指定属性是否应被序列化
- 可以序列化为属性或元素
- 通过属性进行控制
这种方法具有以下缺点:
- 无法在运行时控制格式
- 格式的更改需要代码更改,因此需要重新编译、重新部署代码
使用 IXmlSerializable 接口
自定义序列化与反序列化的第二种技术是通过实现 IXmlSerializable
接口。Product
类实现了 IXmlSerializable
。ReadXml()
方法将在反序列化过程中被调用,WriteXml()
方法将在序列化对象时被调用。
实体类不需要类级别或属性/字段级别的任何属性。运行时将自动考虑在序列化与反序列化过程中所有的 public
属性和字段。
在 WriteXml()
方法中,我们可以决定如何序列化属性。当您使用 XmlWriter
写入属性时,XML 文档将由属性构成。以下代码片段将属性作为属性写入。
public void WriteXml(XmlWriter writer)
{
writer.WriteAttributeString("Id", Id);
writer.WriteAttributeString("Name", Name);
writer.WriteAttributeString("Quantity", Quantity.ToString());
writer.WriteAttributeString("Rate", Rate.ToString());
}
在 ReadXml()
方法中,您需要按照创建 XML 文档的相同顺序执行从 XmlReader
读取的操作。
public void ReadXml(XmlReader reader)
{
Id = reader.GetAttribute("Id");
Name = reader.GetAttribute("Name");
Quantity = int.Parse(reader.GetAttribute("Quantity"));
Rate = decimal.Parse(reader.GetAttribute("Rate")) ;
}
优点和缺点
这种方法具有以下优点:
- 以 XML 格式序列化,序列化为属性或元素。有利于互操作性。
- 对序列化和反序列化有更好的灵活性和控制力。我们可以包含一些自定义逻辑,并决定是否序列化/反序列化某个属性。
这种方法具有以下缺点:
- 每个实体都需要
ReadXml()
和WriteXml()
实现。在拥有更多实体的系统中,在所有实体中实现ReadXml()
和WriteXml()
逻辑将花费大量时间。
使用 ISerializable 接口
还可以通过在类上实现 ISerializable
接口来实现自定义序列化。当序列化对象实例时,运行时将调用 GetObjectData()
方法。在此方法中,您需要添加需要序列化的属性和值。当您反序列化对象时,需要一个特殊的重载构造函数,在该构造函数中获取值并填充您的属性。数据将使用 Binary formatter 进行序列化,因此性能会比其他方法更好,但对象不可互操作。
这与 IXmlSerializable
接口非常相似,但区别在于您将处理 SerializationInfo
实例而不是 XmlReader
和 XmlWriter
类。
使用 ISerializable
接口进行序列化
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Id", _id);
info.AddValue("OrderDate", _orderDate);
info.AddValue("Customer", customer);
info.AddValue("Products", products);
}
使用 ISerializable
接口进行反序列化
public Order(SerializationInfo info, StreamingContext context)
{
_id = info.GetString("Id");
_orderDate = info.GetDateTime("OrderDate");
customer = (Customer)info.GetValue("Customer", typeof(Customer));
products = (Product[])info.GetValue("Products", typeof(Product[]));
}
Order
类有一个对象图。它包含一个 customer
实例和一个 Product
实例数组。
调用序列化过程的代码如下:
MemoryStream memoryStream = new MemoryStream();
StreamWriter writer = new StreamWriter(memoryStream);
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(memoryStream, order);
ASCIIEncoding encoding = new ASCIIEncoding();
byte[] bytes = memoryStream.ToArray();
调用反序列化过程的代码如下:
MemoryStream memoryStream = new MemoryStream(bytes);
BinaryFormatter formatter = new BinaryFormatter();
Order order = (Order) formatter.Deserialize(memoryStream);
优点和缺点
这种方法具有以下优点:
- 以二进制格式序列化,获得最大性能。
- 对序列化和反序列化过程有更好的灵活性和控制力。我们可以包含一些自定义逻辑,并决定是否序列化/反序列化某个属性。
这种方法具有以下缺点:
- 每个实体都需要
GetObjectData()
和专门的构造函数实现。在拥有更多实体的系统中,在所有类中实现这些方法将花费大量时间。
关于代码
该应用程序有三个窗体:
客户
产品
Order
每个窗体都演示了一种不同的技术。窗体将序列化实例并在文本框中显示序列化数据,然后反序列化函数将创建实例并填充控件。您可以更改序列化数据的值,并观察反序列化过程拾取更改后的数据并填充控件。Order
窗体以二进制格式进行序列化和反序列化,这将在窗体中显示。
Customer
窗体使用 Customer
类。该窗体演示了基于属性的序列化。

Product
窗体使用 Product
类。该窗体演示了使用 IXmlSerializable
接口的序列化。

Order
窗体使用 Order
类,该类内部包含 Customer
和 Product[]
。该窗体演示了使用 ISerializable
接口和二进制格式的序列化。

结论
.NET Framework 提供了许多选项来执行类的实例的自定义序列化和反序列化。根据灵活性、互操作性、代码可维护性、涉及的工作量等各种因素,我们可以选择适当的技术。
在下一篇文章中,我将讨论如何从头开始完全实现一个序列化引擎,并将互操作性和灵活性作为关键要求。
阅读愉快!编码愉快!
历史
- 2008 年 3 月 6 日:初始发布