细说C#中的系列化与反系列化的基本原理和过程
虽然我们平时都使用第三方库来进行序列化和反序列化,用起来也很方便,但至少得明白序列化与反序列化的基本原理。
懂得人就别看了!
注意:从.NET Framework 2.0 开始,序列化格式化器类SoapFormatter已过时。请改用 BinaryFormatter。
- 序列化:把目标对象转换为字节流的过程
- 反序列化:把字节流转换回对象的过程
序列化与反序列化需要一个序列化器类:即System.Runtime.Serialization.Formatters.Binary.BinaryFormatter类
实例化一个格式化器BinaryFormatter类:BinaryFormatter binaryFormatter = new BinaryFormatter();
一、序列化:
binaryFormatter.Serialize(Stream stream, Object obj);
- 该方法把一个或多个目标对象序列化为字节流并保存到目标流对象stream中。需要提供一个目标流对象stream(stream对象可以是基类Stream的派生类:
FileStream、MemoryStream、NetWorkStream等流对象);
序列化过程:
- 序列化时,首先判断每个对象的类型定义是否应用了可序列化
[Serializable]特性,否则抛出异常。 - 其次调用对象中那些被标记了
[OnSerializing]特性的所有方法。(即:执行序列化前,先调用该方法做一些事情) - 接下来,利用反射机制来取得每个目标对象的类型中所有需要序列化的实例字段的信息,并读取对应字段的值保存到字节流中。
- 序列化时,还保存了目标类型的全名、定义类型程序集的全名,作为标识信息保存到流中(用于反序列化)。
- 最后,调用所有被标记了
[OnSerialized]特性的方法。(即:序列化完成后,调用该方法做一些事情)
二、反序列化:
SomeObject obj = (SomeObject)binaryFormatter.Deserialize(Stream stream);
- 该方法把目标流对象stream中的字节流反序列化为Object对象,可根据需求进行转型为对应的目标对象
反序列化过程:
- 反序列化时,首先从字节流中读取程序集的标识信息,然后调用
System.Reflection.Assembly类的Load()方法加载该程序集到当前的AppDomain中,只有当程序集加载成功后,格式化器才能在程序集中查找是否存在与需要被反序列化的对象的类型信息相同的类型 - 找到类型后,调用对象中那些被标记了
[OnDeserializing]特性的所有方法 - 接下来利用该类型创建实例
- 然后从字节流中获取对应字段的值对该实例进行初始化。
- 最后,调用所有被标记了
[OnDeserialized]特性的方法。
此过程中若找不到匹配的类型,则会抛出异常终止反序列化。但是,我在利用第三方类库反序列化JSON文件时,JSON文件并不存在对象名和程序集名的标识信息,第三方类库的格式化器应该是根据各个对象的成员名称、类型,在当前AppDomain中的所有程序集中进行查找匹配的类型。
注意:
- 最好把序列化或反序列化的过程放进try块中,用catch (SerializationException e)块处理需要处理的异常,并在finally块中释放资源(关闭流)。
- 可以把流的定义放在using语句的“()”中,把序列化反序列化代码放在using块内,来达到自动释放资源的目的。
三、序列化配置
在执行序列化和反序列化之前,可以对格式化器的Context属性进行设置,Context属性是一个StreamingContext结构
binaryFormatter.Context = new StreamingContext(StreamingContextStates.Remoting);//指定为来源和目的地是远程的
StreamingContext结构的属性有两个:
State属性,是一个枚举类型StreamingContextStates的值,用来说明序列化和反序列化的对象的来源和目的地Context属性,一个上下文对象的引用,包含了用户希望得到的任何上下文信息
StreamingContext结构存在的意义就是通过State属性的值描述给定的序列化流的源和目标,并利用Context属性提供一个由调用方定义的附加上下文。
(因为同一个被序列化好的对象可能会有不同的目的地,如不同机器,不同进程中等,我们就可以通过State属性的状态来标识对象的目的地)
示例:利用序列化和反序列化,定义一个深度克隆一个对象的方法
public object DeepCloneObject(object oldObj)
{
try
{
using (MemoryStream stream = new MemoryStream())
{
BinaryFormatter bf = new BinaryFormatter();
bf.Context = new StreamingContext(StreamingContextStates.Clone);
//把对象序列化到流中
bf.Serialize(stream, oldObj);
//在进行反序列化前,需要先定位到内存流的起始位置
stream.Position = 0;
//将内存流中的内容反序列化成新的对象
return bf.Deserialize(stream);
}
}
catch(SerializationException e)
{
Console.WriteLine("序列化和反序列化时出错了,错误信息为:" + e.Message);
//不做处理,重新抛出原异常对象
throw;
}
finally
{
//using中的stream对象会被自动释放,这里不要对它处理
}
}
四、如果有些类型的成员无法进行序列化或反序列化,则可以为该类型实现接口ISerializationCallbackReceiver,该接口定义了两个方法:
比如在Unity3d中,无法序列化枚举类型的成员,就需要把他标记为[NonSerialized]不对他处理,然后定义一个该成员的字符串形式的成员,通过该字符串和对应枚举类型的转换,就可以达到序列化的目的了。如下代码:
public enum ItemType
{
left,
right
}
public class ScoreModel:ISerializationCallbackReceiver
{
public int Score { get; set; }
[NonSerialized]
public ItemType itemType;
public string itemTypeString;
//反序列化完成自动后调用
public void OnAfterDeserialize()
{
itemType = (ItemType)Enum.Parse(typeof(ItemType), itemTypeString);
}
//进行序列化之前自动调用
public void OnBeforeSerialize()
{
itemTypeString = itemType.ToString();
}
}
五、关于序列化为派生类类型的情况
即使是第三方类库,可能也无法处理这种情况,比如:从JSON中反序列化后得到的对象的成员是一个基类类型的,如果直接把该成员强转为我们实际需要的子类类型,正常情况下是不行的,这时就需要自定义一些方法来实现了,利用协变在序列化过程中返回子类类型的对象给基类类型,这样得到的基类类型就可以强转为子类类型了。具体实现代码,大家可以搜起来!
细说C#中的系列化与反系列化的基本原理和过程的更多相关文章
- Java Serializable系列化与反系列化
[引言] 将 Java 对象序列化为二进制文件的 Java 序列化技术是 Java 系列技术中一个较为重要的技术点,在大部分情况下,开发人员只需要了解被序列化的类需要实现 Serializable 接 ...
- C#对泛型List<T>系列化与反系列化
练习一个小例子,在C#中,怎样对泛型List<T>数据集进行系列化与反系列化.我们先了解msdn提供的JavaScriptSerializer类: JavaScriptSerializer ...
- 细说.NET 中的多线程 (一 概念)
为什么使用多线程 使用户界面能够随时相应用户输入 当某个应用程序在进行大量运算时候,为了保证应用程序能够随时相应客户的输入,这个时候我们往往需要让大量运算和相应用户输入这两个行为在不同的线程中进行. ...
- 细说.NET中的多线程 (二 线程池)
上一章我们了解到,由于线程的创建,销毁都是需要耗费大量资源和时间的,开发者应该非常节约的使用线程资源.最好的办法是使用线程池,线程池能够避免当前进行中大量的线程导致操作系统不停的进行线程切换,当线程数 ...
- [Asp.net]c#中的斜杠和反斜杠
引言 在外地出差,给客户部署项目,三家做的项目要在一起集成,这就造成数据格式不同,路径中的斜杠和反斜杠造成了很大的问题. 查了一下这方面的资料,这里做一些记录,算是一个小结吧. 正斜杠(/)与反斜杠( ...
- 常用路径 URL 中的斜杠与反斜杠
常用路径中的斜杠与反斜杠... ------------------------------ 斜杠:反斜杠:======================电脑能识别的斜杠有两种:斜杠分正斜杠(forwa ...
- Oracle 11G R2 RAC中的scan ip 的用途和基本原理【转】
Oracle 11G R2 RAC增加了scan ip功能,在11.2之前,client链接数据库的时候要用vip,假如你的cluster有4个节点,那么客户端的tnsnames.ora中就对应有四个 ...
- APS中生产计划排程模块的基本原理
高级计划系统(APS)作为ERP和MES的补充,用于协调物流.开发瓶颈资源和保证交货日期. APS包括需求和供应计划.运输和生产计划排程等各种供应链计划模块,本文主要介绍APS中生产计划排程模块的基本 ...
- idea在maven中引入了jar包依赖,但是编译过程中报出XXX程序包不存在,已解决
idea在maven中引入了jar包依赖,但是编译过程中报出XXX程序包不存在 1. 报错具体情况 2. Project Structure中的Libraries没有任何红色波浪线 3. 发现自己要引 ...
随机推荐
- 高吞吐koa日志中间件
Midlog中间件 node服务端开发中少不了日志打点,而在koa框架下的日志打点在多进程环境中日志信息往往无法对应上下文,而且在高并发下直接进行写buffer操作(内核调用writev)也会造成内存 ...
- 1164: 零起点学算法71——C语言合法标识符(存在问题)
1164: 零起点学算法71——C语言合法标识符 Time Limit: 1 Sec Memory Limit: 64 MB 64bit IO Format: %lldSubmitted: 10 ...
- 对quartz定时任务的初步认识
已经好久没有写技术博文了,今天就谈一谈我前两天自学的quartz定时任务吧,我对quartz定时任务的理解,就是可以设定一个时间,然后呢,在这个时间到的时候,去执行业务逻辑,这是我的简单理解,接下来看 ...
- MySQL关于check约束无效的解决办法
首先看下面这段MySQL的操作,我新建了一个含有a和b的表,其中a用check约束必须大于0,然而我插入了一条(-2,1,1)的数据,其中a=-2,也是成功插入的. 所以MySQL只是check,但是 ...
- Java容器的各种总结
Java容器指的是List,Set,Map这些类.由于翻译的问题,问到集合,Collection这些指的都是它们几个. List ArrayList 随机访问快 LinkedList 插入删除快 这个 ...
- SVG如何做圆形图片
SVG如何做圆形图片 2016年5月31日17:30:48 提到圆形图片,大家首先想到的一定是border-radius,但在SVG中这些方法很难起效,下面方法适合SVG中制作任意规则与不规则的图形. ...
- jQuery / zepto ajax 全局默认设置
jQuery / zepto 的 $.ajax 方法需要配置很多选项, 有些是很常用的每个 ajax 请求都要用到的, 可以全局设置, 避免每次都写. 注意: 此处用的 jQuery 版本是 1.8. ...
- 读《effective C++》2
条款03:尽可能使用const(Use const whenever possible) 1.const == 奇妙的事 const的一件奇妙的事是,他允许你定义一个约束,(告诉编译器,这是一个“不该 ...
- C# TreeGridView 实现进程列表
效果如图 0x01 获取进程列表,使用Win32Api规避"拒绝访问"异常 public List<AppProcess> GetAppProcesses() { In ...
- Python、PyCharm的安装及使用方法(Mac版)
上周跟朋友喝咖啡时聊起我想学Python,她恰好也有这个打算,顺便推荐了一本书<编程小白的第1本Python入门书>,我推送到Kindle后,随手翻看了下,用语平实,简洁易懂. 之前在R语 ...