作者:BB_KYLE | 来源:互联网 | 2023-08-14 13:42
篇首语:本文由编程笔记#小编为大家整理,主要介绍了CLR 序列化相关的知识,希望对你有一定的参考价值。
序列化的概念
序列化 是将对象或对象图转化成字节流的过程。
反序列化 是将字节流转换回对象图的过程。
序列化的好处是,一旦将对象序列化成内存中的字节流,就可以方便的以一些更有用的方式处理数据,比如:
.net 可以支持二进制序列化、XML和SOAP 序列化、JSON 序列化。其中二进制序列化功能最强大,类型保真,XML只序列化公共属性和字段,JSON 只序列化公共属性。JSON 广泛用于web 上共享数据,是一个开放式标准。
本篇只介绍.net 中二进制序列化。
如何使类型可序列化
看一个序列化应用的例子:
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
internal static class QuickStart {
public static void Main() {
格式化器是实现了 System.Runtime.Serialization.IFormatter 接口的类型,它知道如何序列化和反序列化对象图。BinaryFormatter 就是FCL 提供的格式化器。
BinaryFormatter 格式化器的Serialize 方法将对象转为流数据,它的Deserialize 方法将流数据转为对象。
格式化器的Deserialize 方法检查流的内容,构造流中所有对象的实例,并初始化所有这些对象中的字段。
反序列化的过程是,格式化器首先获取程序集标识信息,并通过调用System.Reflection.Assembly 的load 方法,确保程序集已加载到正在执行的AppDomain中。如果程序集中没有反序列化实例的类型就会报错。
下面是一个利用序列化实现对象的深拷贝:
private static Object DeepClone(Object original) {
下面说一说如何让类型可序列化
很简单,像下面这样向类型应用定制特性 System.SerializableAttribute 。
[Serializable]
internal struct Point {public Int32 x, y;}
SerializableAttribute 这个定制特性只能应用于引用类型 class、值类型 struct、枚举类型 enum、和委托 delegate。
枚举类型和 委托类型总是可序列化的,不必显示应用特性。
SerializableAttribute 不会被派生类型继承。
如何控制序列化和反序列化
将SerializableAttribute 定制特性应用于类型,所有实例字段都会被序列化。如果我不想让某一个字段序列化怎么办呢?。还有哪些情况不应该序列化呢?
使用 System.NonSerializedAttribute 定制特性指出类型中不应该序列化的字段。
[Serializable]
internal class Circle{
private Double m_radius;
除了NonSerializedAttribute 定制特性之外,FCL 还提供了其他控制序列化和反序列化的特性。这些特性应用于不同的场景,我这里只简单列举一些常用的。
System.Runtime.Serialization
格式化器如何序列化类型实例
FCL在 System.Runtime.Serialization 命名空间提供了一个FormatterServices 类型。该类型只包含静态方法,而且该类型不能实例化。以下步骤描述了格式化器如何自动序列化类型,应用了 SerializableAttribute 特性的对象。
1 格式化器调用 FormatterServices 的 GetSerializableMembers方法:
通过反射获取public 和private实例字段,将它们放到MemberInfo 对象构成的数组中。
2 对象被序列化,System.Reflection.MemberInfo 对象数组传给 FormatterServices静态方法GetObjectData:
3 格式化器将程序 集标识和类型的完整名称写入流中。
4 格式化器遍历两个数组中的元素,将每个成员的名称和值写入流中。
以下步骤描述格式化器如何自动反序列化类型应用了SerializableAttribute 特性的对象。
格式化器从流中读取程序集标识和完整类型名称。将该程序集加载到AppDomain中。
格式化器调用 FormatterServices 的静态方法GetUninitializedObject:
这个方法为新对象分配内存,但不为对象调用构造器。
格式化器现在构造并初始化一个MemberInfo 数组,具体做法和前面一样,都是调用 FormatterServices的 GetSerializableMembers方法。
格式化器根据流中包含的数据创建并初始化一个Object 数组。
将新分配对象、MemberInfo数组以及并行Object数组的引用传给FormatterServices的静态方法 PopulateObjectMembers:
这个方法遍历数组,将每个字段初始化成对应的值,到此为止,对象就算是被彻底反序列化了。
控制序列化/反序列化的数据
如果前面提到的特性:OnSerialized、OnDeserializing、OnDeserialized等,它们不能满足你想要的功能。另外格式化器使用的是反射,反射会影响程序的性能。为了对序列化/和反序列化的数据进行完全的控制,避免使用反射,你的类型可以实现 System.Runtime.Serialization.ISerializable 接口。它的定义如下:
public interface ISerializable {
void GetObjectData(SerializationInfo info , StreamingContext context);
}
它只有一个方法GetObjectData,实现这个接口的大多数类型还实现了一个特殊的构造器。
类型一旦实现了这个接口,所有派生类型也要实现它,而且派生类型必须保证调用基类的GetObjectData方法和特殊构造器。
格式化器序列化对象图时会检查每个对象。如果发现一个对象的类型实现了 ISerializable接口,就会忽略所有定制特性,改为构造新的System.Runtime.Serialization.SerializationInfo 对象。该对象包含了要为对象序列化的值的集合。
要实现ISerializable 但基类没有实现怎么办?
这种情况下,派生类必须手动序列化基类的字段,具体的做法就是获取它们的值,并把者些值添加到SerializationInfo 集合中。然后,在你的特殊构造器中,还必须从集合中取出值,以某种方式设置基类的字段。如果基类的字段是public 或 protected 的可以实现,但是private 字段,几乎不能实现。
流上下文
一组序列化好的对象可以有许多目的地:同一个进程、同一台机器的不同进程、不同机器上的不同进程等。
某些情况下,一个对象想知道它要在什么地方反序列化,从而以不同的方式生成它的状态。
例如,如果对象中包装了Windows 信号量对象,如果它知道要反序列化到同一个进程中,就可决定对它的内核句柄进行序列化,这是因为内核句柄在一个进程中有效。 但如果要反序列化到同一台计算机的不同进程中,就可决定对信号量的字符串名称进行序列化。 最后,如果要反序列化到不同计算机的进程,就可决定抛出异常,因为信号量只在一台机器中有效。
格式化器中的大量方法都接受一个StreamingContext 类型,StreamingContextStates 结构是一个非常简单的值类型,它只提供两个公共只读属性。
接受一个StreamingContext 结构的方法能检查State 属性的位标志,判断要序列化/反序列化的对象的来源或目的地。
IFormatter 接口定义了StreamingContext类型的可读/可写性Context。构造格式化器时,格式化器会初始化它的 Context属性,将StreamingContextStates 设为All,对额外状态对象的引用设为null。
.net 由于元数据和IL 中间语言以及JIT 编译等特点使它在序列化方面有更多的灵活性。在.net 中类型还能序列化为不同类型,对象也能反序列化为不同的对象。.net 中的格式化器还允许不是 “类型实现的一部分” 的代码重写该类型 “序列化和反序列化其对象” 的方式。