虽然我们平时都使用第三方库来进行序列化和反序列化,用起来也很方便,但至少得明白序列化与反序列化的基本原理。

懂得人就别看了!

注意:从.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#中的系列化与反系列化的基本原理和过程的更多相关文章

  1. Java Serializable系列化与反系列化

    [引言] 将 Java 对象序列化为二进制文件的 Java 序列化技术是 Java 系列技术中一个较为重要的技术点,在大部分情况下,开发人员只需要了解被序列化的类需要实现 Serializable 接 ...

  2. C#对泛型List<T>系列化与反系列化

    练习一个小例子,在C#中,怎样对泛型List<T>数据集进行系列化与反系列化.我们先了解msdn提供的JavaScriptSerializer类: JavaScriptSerializer ...

  3. 细说.NET 中的多线程 (一 概念)

    为什么使用多线程 使用户界面能够随时相应用户输入 当某个应用程序在进行大量运算时候,为了保证应用程序能够随时相应客户的输入,这个时候我们往往需要让大量运算和相应用户输入这两个行为在不同的线程中进行. ...

  4. 细说.NET中的多线程 (二 线程池)

    上一章我们了解到,由于线程的创建,销毁都是需要耗费大量资源和时间的,开发者应该非常节约的使用线程资源.最好的办法是使用线程池,线程池能够避免当前进行中大量的线程导致操作系统不停的进行线程切换,当线程数 ...

  5. [Asp.net]c#中的斜杠和反斜杠

    引言 在外地出差,给客户部署项目,三家做的项目要在一起集成,这就造成数据格式不同,路径中的斜杠和反斜杠造成了很大的问题. 查了一下这方面的资料,这里做一些记录,算是一个小结吧. 正斜杠(/)与反斜杠( ...

  6. 常用路径 URL 中的斜杠与反斜杠

    常用路径中的斜杠与反斜杠... ------------------------------ 斜杠:反斜杠:======================电脑能识别的斜杠有两种:斜杠分正斜杠(forwa ...

  7. Oracle 11G R2 RAC中的scan ip 的用途和基本原理【转】

    Oracle 11G R2 RAC增加了scan ip功能,在11.2之前,client链接数据库的时候要用vip,假如你的cluster有4个节点,那么客户端的tnsnames.ora中就对应有四个 ...

  8. APS中生产计划排程模块的基本原理

    高级计划系统(APS)作为ERP和MES的补充,用于协调物流.开发瓶颈资源和保证交货日期. APS包括需求和供应计划.运输和生产计划排程等各种供应链计划模块,本文主要介绍APS中生产计划排程模块的基本 ...

  9. idea在maven中引入了jar包依赖,但是编译过程中报出XXX程序包不存在,已解决

    idea在maven中引入了jar包依赖,但是编译过程中报出XXX程序包不存在 1. 报错具体情况 2. Project Structure中的Libraries没有任何红色波浪线 3. 发现自己要引 ...

随机推荐

  1. DC平衡双向控制串行器 转接IC GM8913:LVTTL转FPD-LINK III芯片

    1 概述     GM8913型DC平衡双向控制串行器,其主要功能是实现将10或12位并行控制信号和一路时钟信号串行为一路2.8Gbps高速串行数据:同时接收低速通道信号实现模式配对的功能.芯片内部集 ...

  2. 如何通过 HSB 颜色模式构建夜间模式

    中国睡眠研究会发布的<2017 年中国青年睡眠现状报告>显示,大约 90% 的人在睡前离不开电子产品. 不知道大家有没有感觉到普通的亮色界面会让我们在夜间使用的时侯感到刺眼,长时间使用会感 ...

  3. Java中集合框架,Collection接口、Set接口、List接口、Map接口,已经常用的它们的实现类,简单的JDK源码分析底层实现

    (一)集合框架: Java语言的设计者对常用的数据结构和算法做了一些规范(接口)和实现(实现接口的类).所有抽象出来的数据结构和操作(算法)统称为集合框架. 程序员在具体应用的时候,不必考虑数据结构和 ...

  4. 1020. Tree Traversals

    Suppose that all the keys in a binary tree are distinct positive integers. Given the postorder and i ...

  5. 基础:获得servletAPI

    package com.learning.web.action; import javax.servlet.ServletContext; import javax.servlet.http.Http ...

  6. 关于使用ModelSim中编写testbench模板问题

    对于初学者来说写Testbench测试文件还是比较困难的,但Modelsim和quartus ii都提供了模板,下面就如何使用Modelsim提供的模板进行操作. Modelsim提供了很多Testb ...

  7. html 数字不转行问题

    代码如下 <div style="width:20px;height:20px"> 111111111111111111111111111111111111111111 ...

  8. iOS 关于定位你该注意的那些事

      其实现在对于一个APP来说,定位用户的位置是件很容易的事情,有三种解决方案供您选择: (1)原生 (2)高德地图 (3)百度地图 1.解决方案的选择   其实单说iOS开发来说应用哪种方案都无所谓 ...

  9. Linux - 进程间通信 - 命名管道

    1.命名管道的特点: (1)是管道,可用于非血缘关系的进程间的通信 (2)使用命名管道时,梁金成需要用路径表示通道. (3)命名管道以FIFO的文件形式存储于文件系统中.(FIFO:总是按照先进先出的 ...

  10. python——网络编程

    Socket socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求. sock ...