序列化是将对象或者对象图(一堆有包含关系的对象)转换成字节流的过程。而反序列化就是将字节流转为对象或对象图。

主要用于保存、传递数据,使得数据更易于加密和压缩。

.NET内建了出色的序列化和反序列化支持。

上一个简单的小例子:

using System.Runtime.Serialization.Formatters.Binary;

namespace MyTest
{ class Program
{
static void Main(string[] args)
{
MemoryStream stream = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(stream, 某个对象); Console.Read();
} }
}

反序列化就是formatter.Deserialize(stream)。(注意将stream的position置0)

以上的流不仅仅可以使MemoryStream,也可以是Steam类派生的任意类,比如FileStream或NetworkSteam。

这是序列化为字节流,如果要序列化为XML就应该用System.Xml.Serialization.XmlSerializer和System.Runtime.Serialization.DataContractSerializer类。

序列化时,会将类型的全名和类型定义的程序集的全名写入流,反序列化时根据这些信息来确保程序集已加载到正在执行的AppDomain,从而反序列化为相应的类。

使类型可序列化和控制它的序列化

如果要使类型可序列化,那么必须要在要序列化的对象的类型之上加上[Serialzable]。

如果一个类要可序列化,那么其基类必须能可序列化。

而有的字段如果不需要将其序列化,可以在字段上使用[NonSerialized]特性。

如果需要在反序列化后自动调用某些函数,可以在函数上加上[OnDeserialized]特性。(同样还有[OnSerializing],[OnSeralized]和[OnDeserializing]特性,用来标记其它函数,有其对应的效果)

一个对象序列化为流后,它的类新增了一个字段,那么反序列化时会报异常。处理方案就是将新增的字段加上[OptionalField]特性,当格式化器发现该特性应用与某个字段后,就不会对流中没有此字段而抛出异常。

将以上玩法结合起来的小例子:

[Serializable]//指定某个类可以序列化
public class Troy {
private string _name;
[OptionalField]//指定这个字段可以在反序列化时缺少
private string sex;
[NonSerialized]//指定此字段不会被序列化
private string _fullName; public Troy(string name) {
this._name = name;
this._fullName = "Troy" + name;
}
[OnDeserialized]//指定次函数在反序列化后自动执行
private void 在反序列化后做点什么的函数(StreamingContext context) {
this._fullName = "Troy" + this._name;
}
}

除了用OnDeserialized这种特性的方式来实现对序列化和反序列化时数据的控制,还可以用另外一种方法:让类实现ISerializable接口。(它只有一个方法GetObjectData,实现这个接口的大多数类型还实现了一个特殊的构造器)

(这种方法看不懂可以先看下一个关键点格式化器如何对对象进行序列化之后,再来看这种方式)

优点:

  • 用特性的方式在极少数情况下不能提供想要的全部控制,所以可以选择实现System.Runtime.Serialization.ISerializable接口的方式
  • 特性的方式用的是反射,速度比较慢,增大序列化/反序列化对象所花的时间

缺点:

  • 一旦实现ISerializable接口,那么类的派生类也必须实现它,且派生类必须保证调用基类的GetObjectData和特殊构造器
  • 一旦实现此接口,就不能删除,否则会失去与派生类型的兼容性。(所以密封类实现此接口最让人放心)

序列化是,格式化器如果发现一个类实现了ISerializable接口,那么就会忽略所有定制特性,改为构造新的SerializationInfo对象,将对象内容和信息在GetObjectData中添加到SerializationInfo中,然后再序列化SerializationInfo。

而在反序列化时,格式化器就会判断是否实现了ISerializable接口,如果实现了,那么就会调用我们自己实现的一个特殊化构造器,它获取一个SerializationInfo对象引用,由我们去实现将这个SerializationInfo通过其实例函数取值得到对象的值。

前面说道这种方式的缺点时提到派生类必须保证调用基类的GetObjectData和特殊构造器,而如果其基类并没有实现GetObjectData和特殊构造器,那么就只能自己手动用FormatterServices中的函数去取基类的值去给SerializationInfo中,反序列化的时候还要自己去手动从SerializationInfo中取值并赋值给基类。如果基类中有private这类字段,那么就不可能实现了,只能用前面那种加特性的方法了。

格式化器如何对对象进行序列化

为简化格式化器的操作,FCL在System.Runtime.Serialization命名空间提供了一个FormatterServices静态类。

序列化步骤:

  1. 格式化器调用FormatterServices的GetSerializableMembers方法。(此方法获取类的实例字段的MesberInfo类型的数组A)
  2. 调用FormatterServices的GetObjectData函数,参数为A。(此方法得到与数组A对应的实例的值的Object数组B)
  3. 格式化器将程序集标识和类型完整名称写入流中。
  4. 格式化器遍历数字A和数组B,将每个成员的名称和值写入流中。

反序列化步骤:

  1. 格式化器从流中读取了程序集标识和完整类型名称。如果程序集没有加载到AppDomain中就加载它。夹在后,格式化器将程序集表示和类型全名传给FormatterServices的GetTypeFromAssembly。(此方法返回一个Type对象)、
  2. 然后格式化器调用FormatterServices的GetUninitializedObject方法.(此方法返回了一个新对象C,为新对象分配内存,但是不调用构造器。对象所有字节都被初始化为null或0)
  3. 格式化器构造并初始化一个MemberInfo数组A,和前面一样调用FormatterServices的GetSerializableMembers方法。
  4. 格式化器根据流中包含的数据创建并初始化一个Object数组B
  5. 将新分配的对象C、数组A,数组B传递给FormatterServices的PopulateObjectMembers方法。(此方法结合这三个东西,返回最终的对象)

流上下文

本章提到的大量方法都接收受一个StreamContext(流上下文)。

这是一个Struct,只提供两个公共只读属性:State和Context。

State是一组位标志,指定了要序列化和反序列化的对象的来源或目的地。(可根据任意一种位标志来new一个StreamContext)

Context是一个对象引用,该对象中包含用户希望的任何上下文信息。

序列化代理

所谓序列化代理,就是定义一个"代理类型",它接管对现有类型进行序列化和反序列化的行动,然后向格式化器登记该代理类型的实例,告诉格式化器代理类型要作用于哪个类型。

实现序列化代理,主要是出于两个方面考虑:

  • 允许开发人员序列化最初没有设计成要序列化的类型
  • 允许开发人员提供一种方式将类型的一个版本映射到类型的一个不同的版本。

实现:

序列化代理类型必须实现System.Runtime.Serialization.ISerializationSurrogate接口。

它有GetObjectData和SetObjectData两个方法。

这里的玩法实际上与ISerializable接口的玩法差不多。

以下为使用序列化代理类型的方式

        MemoryStream stream = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
//构造一个代理选择器对象
SurrogateSelector ss = new SurrogateSelector();
//告诉代理选择器为类型A的类型使用我们的代理
ss.AddSurrogate(typeof(A), formatter.Context, new 我们实现的代理类());//可多次调用来登记多个代理
//告诉格式化器使用代理选择器
formatter.SurrogateSelector = ss;
//接着正常进行序列化和反序列化即可

反序列化对象时重写程序集/类型

构建继承System.Runtime.Serialization.SerializationBinder抽象类的绑定类,此抽象类需要实现BindToType方法去绑定具体的程序集和类型。

然后只需要在序列化的时候将格式器的Binder属性设置为实现的派生类的实例即可,格式化器会反序列化的时候自动调用绑定器的BindToType方法,BindToType会去判断实际要构建哪个程序集的哪个类再进行相应操作。

【C#进阶系列】24 运行时序列化的更多相关文章

  1. 《CLR Via C#》读书笔记:24.运行时序列化

    一.什么是运行时序列化 序列化的作用就是将对象图(特定时间点的对象连接图)转换为字节流,这样这些对象图就可以在文件系统/网络进行传输. 二.序列化/反序列化快速入门 一般来说我们通过 FCL 提供的 ...

  2. 重温CLR(十八) 运行时序列化

    序列化是将对象或对象图转换成字节流的过程,反序列化是将字节流转换回对象图的过程.在对象和字节流之间转换是很有用的机制. 1 应用程序的状态(对象图)可轻松保存到磁盘文件或数据库中,并在应用程序下次运行 ...

  3. 虚拟机系列 | JVM运行时数据区

    本文源码:GitHub·点这里 || GitEE·点这里 一.内存与线程 1.内存结构 内存是计算机的重要部件之一,它是外存与CPU进行沟通的桥梁,计算机中所有程序的运行都在内存中进行,内存性能的强弱 ...

  4. 《深入浅出MFC》系列之运行时类型识别(RTTI)

    /********************************************************************************** 发布日期:2017-11-13  ...

  5. clr via c# 运行时序列化

    1,快速了解序列化----windows IO 系统,FileStream,BinaryFormatter,SoapFormatter--不支持泛型. public class SerializeRe ...

  6. C# 运行时序列化

    一. 序列化与反序列的作用 为什么要有序列化呢,考虑下面这种情况,在WINFORM或者更为方便的WPF工程中,当我们进行UI设计时,可以随意的将一个控件剪切/张贴到另外一个地方.操作方便的背后是什么在 ...

  7. (47)C#运行时序列化

    序列化是将对象或对象图转化成字节流的过程.反序列化是将字节流转换回对象图的过程.

  8. C#进阶系列 ---- 《CLR via C#》

      [C#进阶系列]30 学习总结 [C#进阶系列]29 混合线程同步构造 [C#进阶系列]28 基元线程同步构造 [C#进阶系列]27 I/O限制的异步操作 [C#进阶系列]26 计算限制的异步操作 ...

  9. RHCE 系列(二):如何进行包过滤、网络地址转换和设置内核运行时参数

    正如第一部分(“设置静态网络路由”)提到的,在这篇文章(RHCE 系列第二部分),我们首先介绍红帽企业版 Linux 7(RHEL)中包过滤和网络地址转换(NAT)的原理,然后再介绍在某些条件发生变化 ...

随机推荐

  1. SQL Server 数据库设计规范

    数据库设计规范 1.简介 数据库设计是指对一个给定的应用环境,构造最优的数据库模式,建立数据库及其他应用系统,使之能有效地存储数据,满足各种用户的需求.数据库设计过程中命名规范很是重要,命名规范合理的 ...

  2. The Hacker's Guide To Python 单元测试

    The Hacker's Guide To Python 单元测试 基本方式 python中提供了非常简单的单元测试方式,利用nose包中的nosetests命令可以实现简单的批量测试. 安装nose ...

  3. ios 避免循环引用

    类似网络请求的情况下会导致循环引用,但是 如果网络请求的对象是局部变量,就必须用self,不能用weakSelf,否则,一旦当前方法所在对象销毁,那weakSelf就为空了(如果目的是这样,那就另当别 ...

  4. 文件上传之Apache commons fileupload使用

    后台servlet代码:         File file1 = null,file2=null;         String description1 = null,description2 = ...

  5. Atitit 基于sql编程语言的oo面向对象大规模应用解决方案attilax总结

    Atitit 基于sql编程语言的oo面向对象大规模应用解决方案attilax总结 1. Sql语言应该得到更大的范围的应用,1 1.1. 在小型系统项目中,很适合存储过程写业务逻辑2 1.2. 大型 ...

  6. Atitit常见的标准化组织与规范数量jcp ecma iso

    Atitit常见的标准化组织与规范数量jcp ecma iso 1. 常见的标准化组织1 1.1. 重要的基金会apache1 1.2. 美国国家标准学会(American NationalStand ...

  7. easyui datagrid cell title换行

    " " title="' +row.TaskName + "

  8. 谈谈service层在mvc框架中的意义和职责

    mvc框架由model,view,controller组成,执行流程一般是:在controller访问model获取数据,通过view渲染页面. mvc模式是web开发中的基础模式,采用的是分层设计, ...

  9. 让hammer完美支持Pixi.js - 2D webG库

    由于项目改造,采用2D webG的pixi库,那么基于canvas的结构上,事件就是最大的一个问题了 改造的原理很简单,把hammer里面的addEventListeners事件绑定给第三方库代替,事 ...

  10. Neutron 架构 - 每天5分钟玩转 OpenStack(67)

    前面我们讨论了 Neutron 的基本概念,今天我们开始分析 Neutron 的架构. Neutron 架构 与 OpenStack 的其他服务的设计思路一样,Neutron 也是采用分布式架构,由多 ...