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

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

.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. 剑指Offer面试题:1.实现Singleton模式

    说来惭愧,自己在毕业之前就该好好看看<剑指Offer>这本书的,但是各种原因就是没看,也因此错过了很多机会,后悔莫及.但是后悔是没用的,现在趁还有余力,把这本书好好看一遍,并通过C#通通实 ...

  2. 探索c#之storm的TimeCacheMap

    阅读目录: 概述 算法介绍 清理线程 获取.插入.删除 总结 概述 最近在看storm,发现其中的TimeCacheMap算法设计颇为高效,就简单分享介绍下. 思考一下如果需要一个带过期淘汰的缓存容器 ...

  3. 測試大型資料表的 Horizontal Partitioning 水平切割

    FileGroup 檔案群組 :一個「資料庫(database)」可對應一或多個 FileGroup,一個 FileGroup 可由一或多個 file (.ndf) 構成. FileGroup 可讓 ...

  4. Module-Zero之租户管理

    返回<Module Zero学习目录> 开启多租户 租户实体 租户管理者 默认租户 开启多租户 ABP和Module-Zero可以运行多租户或单租户模式.多租户默认是禁用的.我们可以在mo ...

  5. C语言 · 求矩阵各个元素的和

    问题描述 这里写问题描述. 输入格式 测试数据的输入一定会满足的格式. 例:输入的第一行包含两个整数n, m,分别表示矩阵的行数和列数.接下来n行,每行m个正整数,表示输入的矩阵. 输出格式 要求用户 ...

  6. xamarin UWP自定义圆角按钮

    uwp自带的button本身不支持圆角属性,所以要通过自定义控件实现. 通过设置Button的Background=“{x:Null}”设置为Null使背景为空,再设置Button.Content中的 ...

  7. 页面静态化技术Freemarker技术的介绍及使用实例.

    一.FreeMarker简介 1.动态网页和静态网页差异 在进入主题之前我先介绍一下什么是动态网页,动态网页是指跟静态网页相对应的一种网页编程技术.静态网页,随着HTML代码的生成,页面的内容和显示效 ...

  8. uwp 图片切换动画

    最近在学习安卓,LOL自定义战绩项目近乎停工,而且腾旭把界面全改了,好烦.刚好学习到安卓中的图片切换动画,我就想在LOL项目中实现一个.首先上百度查看一下,妈的,资料少的可怜. 还是自己来吧.自定义控 ...

  9. WaitType:CXPACKET

    CXPACKET 等待类型是SQL Server 并发执行一个query时产生的.在run一个big query时,SQL Server充分利用系统的所有资源(CPU,Memory,IO),在最短时间 ...

  10. Android音视频之MediaRecorder音视频录制

    前言: 公司产品有很多地方都需要上传音频视频,今天抽空总结一下音频视频的录制.学习的主角是MediaRecorder类. MediaRecorder类介绍: MediaRecorder类是Androi ...