运行时序列化
序列化是将对象或对象图转换成字节流的过程。反序列化是将字节流转换回对象图的过程。
对象图:代表的是对象系统在特定时间点的一个视图(可以理解为快照)
应用例子:
- 应用程序的状态(对象图)可轻松保存到磁盘文件或者数据库中,并在下次运行时恢复
- 一组对象可以复制到系统的剪切板上,再粘回同一个或另一个应用程序
- 一组对象可克隆 并放到一边作为备份;与此同时,用户操纵一组“主”对象
- 一组对象可轻松地通过网络发送给另一台机器上运行的进程
- 序列化成内存中的字节流后可以方便的进行加密和压缩
- 用于数据持久化的一个主要手段
.NET Framework内建了出色的序列化和反序列化支持
24.1 序列化/反序列化快速入门
[Serializable]
internal static class QuickStart
{
public static void Main()
{
//创建对象图以便把它们序列化到流中
var objGraph = new List<String>[3];
objGraph[0] = new List<String> { "zhangsan", "lisi", "wanger", "mazi" };
objGraph[1] = new List<String> { "zhangsan2", "lisi2", "wanger2", "mazi2" };
objGraph[2] = new List<String> { "zhangsan3", "lisi3", "wanger3", "mazi3" };
Stream stream = SerializeToMemory(objGraph);
stream.Position = 0;
objGraph = null;
//反序列化对象
objGraph = (List<String>[])DeserializeFromMemory(stream);
//objGraph = new List<String>[3];
//for (int i=0;i<3;i++)
//{
// objGraph[i] = (List<String>)DeserializeFromMemory(stream);
//}
foreach (var s in objGraph) {
foreach (var item in s) Console.WriteLine(item);
Console.WriteLine();
}
Console.Read();
}
private static MemoryStream SerializeToMemory(List<string>[] objGraph)
{
//构造流来容纳序列化的对象
MemoryStream stream = new MemoryStream();
//构造序列化格式化器来执行所有真正的工作
BinaryFormatter formatter = new BinaryFormatter();
//告诉序列化格式化器将对象序列化到流中
//foreach (var s in objGraph)
// formatter.Serialize(stream, s);
formatter.Serialize(stream, objGraph);
//将序列化好的对象返回给调用者
return stream;
}
private static Object DeserializeFromMemory(Stream stream)
{
//构造序列化格式化器来做所有真正的工作
BinaryFormatter formatter = new BinaryFormatter();
//告诉格式化器从流中反序列化对象
return formatter.Deserialize(stream);
}
}
FCL提供了两个格式化器:BinaryFormatter 和SoapFormatter(.NET Framework3.5开始已废弃 但可用于调试 因为能生成便于阅读的xml文本)
序列化
序列化对象图需要调用格式化器的Serialize方法,方法有两个参数:
对流对象的引用,标识序列化好的字节放哪里(可以是System.IO.Stream的任何派生类的对象)
对想要序列化的对象图的引用,可以是任何东西,对象图中的所有对象都被序列化到流中(利用反射查看每个对象中的实例字段,任何一个字段引用了其他对象,也会被序列化,算法会确保每个对象都只序列化一次,不出现死循环)
反序列化
DeserializeFromStream将流反序列化为对象图
格式化器的Deserialize方法会检查流的内容,构造流中所有对象的实例,并初始化所有字段使他们具有与当初序列化时相同的值
通常,要将Deserialize方法返回的对象引用转型成应用程序期待的类型
注意点
- 由程序员保证代码为序列化和反序列化使用相同的格式化器(反例:BinaryFormatter序列化 、SoapFormatter反序列化)
- 可将多个对象图序列化到一个流中,反序列化和序列化顺序一样
- 序列化时,类型的全名和类型定义程序集的全名会被写入流(BinaryFormatter默认输出程序集的完整标识,包括程序集的文件名,版本号,语言文化以及公钥信息,反序列化时,格式化器会首先获取程序集标识信息,通过调用反射的Load方法确保程序集已加载到正在执行的AppDomain中)
- 程序集加载好后,格式化器在程序集中查找和要反序列化的对象匹配的类型
- 找不到匹配类型就抛出异常,并不再继续反序列化
- 找到匹配的类型,就创建类型的实例并初始化用流中包含的值对其字段初始化
- 类型中的字段与流中读取的字段名不完全匹配,就抛出SerializtionException异常,并不再继续反序列化
24.2 使类型可序列化
向类型应用特性System.SerializableAttribute
-
序列化对象图时,任何对象不可序列化,格式化器Serialize都会抛出SerializtionException异常 (注意,格式化器是边序列化边检查对象是否可序列化,所以在抛出异常前可能已经有一部分对象序列化到流中,流中会包含已损坏的数据,需要从此中异常状态恢复,方法是先将对象序列化到一个MemoryStream中,如果都成功了,再复制到想要的目标流中)
-
SerializableAttribute特性只能应用于引用类型class,结构体struct,枚举类型enum和委托类型delegate
-
枚举类型和委托类型总是可序列化的,不需显示应用特性,SerializableAttribute特性不会被继承(父类对象可被序列化,子类不应用特性的话子类对象也不能序列化)
重要提示:主要针对可拓展应用程序,自主挂接事件实现拿到程序集的名称,自己拼接路径,使用LoadFrom确保加载程序集、
24.3 控制序列化和反序列化
将SerializableAttribute特性应用于类型,所有的实例字段都会被序列化,但类型可能定义了一些不应被序列化的实例字段
- 字段含有反序列化后变得无效的信息(例如,对象包含Windows内核对象(文件、进程、线程等)的句柄,反序列化后到另一个进程或另一台机器无意义了,因为Windows内核对象是跟进程相关的值)
- 字段含有很容易计算的值,需要筛选出那些无须序列化的字段,减少传输的数据,增强应用程序的性能
例:
[Serializable]
internal class Circle
{
private double m_radius;
[NonSerialized]
private double m_area;
public Circle(double radius)
{
m_radius = radius;
m_area = Math.PI * m_radius * m_radius;
}
[OnDeserialized]
private void OnDeserialized(StreamContent context)
{
m_area = Math.PI * m_radius * m_radius;
//反序列化后调用
}
[OnDeserializing]
private void OnDeserializing(StreamContent context)
{
//反序列化前调用
}
[OnSerialized]
private void OnSerialized(StreamContent context)
{
//序列化后调用
}
[OnSerializing]
private void OnSerializing(StreamContent context)
{
//序列化前调用
}
}
注意:
- 使用这4个属性中的任何一个,方法应将声明为private,以免他被普通代码调用,格式化器运行时有足够的安全权限,能够调用私有方法。
- 反序列化期间,当格式化器看到类型提供的一个方法标记了OnDeserialized特性时,格式化器会将这个对象的引用添加到一个内部列表中。所有对象都反序列化后,格式化器反向遍历列表,调用每个对象的OnDeserialized方法。之所以这样以相反的顺序调用,是因为这样才能使内层对象先于外层对象完成反序列化
- 如果序列化类型的实例,在类型中添加新字段,然后试图反序列化不包含新字段的对象,格式化器会抛出SerializtionException异常,并显示流中要反序列化的数据包含错误的成员数目。 当我们必须要这么做的时候,我们需要为类型中新增的每个字段应用OptionalFieldAttribute特性,然后格式化器看到该特性应用于一个字段时,就不会因为流中的数据不包含这个字段而抛异常
更新:
工作中需要,所以自己写了一个序列化成Json格式以及压缩成流的工具类,具体看博文
https://blog.csdn.net/qq_43565708/article/details/134512162?spm=1001.2014.3001.5501