# 谈谈:.Net中的序列化和反序列化
序列化和反序列化相信大家都经常听到,也都会用, 然而有些人可能不知道:.net为什么要有这个东西以及.net Frameword如何为我们实现这样的机制, 在这里我也是简单谈谈我对序列化和反序列化的一些理解。
## 一、什么序列化和反序列化
**序列化**通俗地讲就是将一个对象转换成一个字节流的过程,这样就可以轻松保存在磁盘文件或数据库中。**反序列化**是序列化的逆过程,就是将一个字节流转换回原来的对象的过程。
然而为什么需要序列化和反序列化这样的机制呢?这个问题也就涉及到序列化和反序列化的用途了,
对于序列化的主要用途有:
* 将应用程序的状态保存在一个磁盘文件或数据库中,并在应用程序下次运行时恢复状态。例如, Asp.net 中利用序列化和反序列化来保存和恢复会话状态。
* 一组对象可以轻松复制到Windows 窗体的剪贴板中,再粘贴回同一个或者另一个应用程序。
* 将对象按值从一个应用程序域中发送到另一个程序域
并且如果把对象序列化成内存中的字节流,就可以利用一些其他的技术来处理数据,例如,对数据进行加密和压缩等。
## 二、序列化和反序列简单使用
.Net Framework 提供二种序列化方式:
* 二进制序列化
* XML 和SOAP序列化
序列化和反序列化的简单使用:
```
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace Serializable
{
[Serializable]
public class Person
{
public string personName;
[NonSerialized]
public string personHeight;
private int personAge;
public int PersonAge
{
get { return personAge; }
set { personAge = value; }
}
public void Write()
{
Console.WriteLine("Person Name: "+personName);
Console.WriteLine("Person Height: " +personHeight);
Console.WriteLine("Person Age: "+ personAge);
}
}
class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.personName = "Jerry";
person.personHeight = "175CM";
person.PersonAge = 22;
Stream stream = Serialize(person);
//为了演示,都重置
stream.Position = 0;
person = null;
person = Deserialize(stream);
person.Write();
Console.Read();
}
private static MemoryStream Serialize(Person person)
{
MemoryStream stream = new MemoryStream();
// 构造二进制序列化格式器
BinaryFormatter binaryFormatter = new BinaryFormatter();
// 告诉序列化器将对象序列化到一个流中
binaryFormatter.Serialize(stream, person);
return stream;
}
private static Person Deserialize(Stream stream)
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
return (Person)binaryFormatter.Deserialize(stream);
}
}
}
```
主要是调用System.Runtime.Serialization.Formatters.Binary命名空间下的BinnaryFormatter类来进行序列化和反序列化,调用反序列化后的结果截图:
![](https://box.kancloud.cn/2016-01-23_56a2eb3a82616.png)
从中可以看出除了标记NonSerialized的其他成员都能序列化,注意这个属性只能应用于一个类型中的字段,而且会被派生类型继承。
SOAP 和XML 的序列化和反序列化和上面类似,只需要改下格式化器就可以了, 这里我就不列出来了。
## 三、控制序列化和反序列化
有两种方式来实现控制序列化和反序列化:
* **通过OnSerializing, OnSerialized,OnDeserializing, OnDeserialized,NonSerialized和OptionalField等属性**
* **实现System.Runtime.Serialization.ISerializable接口**
第一种方式实现控制序列化和反序列化代码:
```
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
namespace ControlSerialization
{
[Serializable]
public class Circle
{
private double radius; //半径
[NonSerialized]
public double area; //面积
public Circle(double inputradiu)
{
radius = inputradiu;
area = Math.PI * radius * radius;
}
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
area = Math.PI * radius * radius;
}
public void Write()
{
Console.WriteLine("Radius is: " + radius);
Console.WriteLine("Area is: " + area);
}
}
class Program
{
static void Main(string[] args)
{
Circle c = new Circle(10);
MemoryStream stream =new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
// 将对象序列化到内存流中,这里可以使用System.IO.Stream抽象类中派生的任何类型的一个对象, 这里我使用了 MemoryStream类型。
formatter.Serialize(stream,c);
stream.Position = 0;
c = null;
c = (Circle)formatter.Deserialize(stream);
c.Write();
Console.Read();
}
}
}
```
运行结果为:
![](https://box.kancloud.cn/2016-01-23_56a2eb3a94af1.png)
**注意**:如果注释掉 **OnDeserialized**属性的话,area字段的值就是0了,因为area字段没有被序列化到流中。
在上面需要序列化的对象中,格式化器只会序列化对象的radius字段的值。area字段中的值不会序列化,因为该字段已经应用了NonSerializedAttribute属性,然后我们用Circle c=new Circle(10)这样代码构建一个Circle对象时,在内部,area会设置一个约为314.159这样的值,这个对象序列化时,只有radius的字段的值(10)写入流中, 但当反序列化成一个Circle对象时,它的area字段的值会初始化为0,而不是约314.159的一个值。为了解决这样的问题,所以自定义一个方法应用**OnDeserializedAttribute**属性。此时的执行过程为:每次反序列化类型的一个实例,格式化器都会检查类型中是否定义了 一个应用了该attribute的方法,如果是,就调用该方法,调用该方法时,所有可序列化的字段都会被正确设置。除了**OnDeserializedAttribute**这个定制attribute,system.Runtime.Serialization命名空间还定义了**OnSerializingAttribute,OnSerializedAttribute**和**OnDeserializingAttribute**这些定制属性。
实现**ISerializable**接口方式控制序列化和反序列化代码:
```
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Permissions;
namespace ControlSerilization2
{
[Serializable]
public class MyObject : ISerializable
{
public int n1;
public intn2;
[NonSerialized]
public String str;
public MyObject()
{
}
protected MyObject(SerializationInfo info, StreamingContext context)
{
n1 = info.GetInt32("i");
n2 = info.GetInt32("j");
str = info.GetString("k");
}
[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("i", n1);
info.AddValue("j", n2);
info.AddValue("k", str);
}
public void Write()
{
Console.WriteLine("n1 is: " + n1);
Console.WriteLine("n2 is: " + n2);
Console.WriteLine("str is: " + str);
}
}
class Program
{
static void Main(string[] args)
{
MyObject obj = new MyObject();
obj.n1 = 2;
obj.n2 = 3;
obj.str = "Jeffy";
MemoryStream stream = new MemoryStream();
BinaryFormatter formatter = new BinaryFormatter();
// 将对象序列化到内存流中,这里可以使用System.IO.Stream抽象类中派生的任何类型的一个对象, 这里我使用了 MemoryStream类型。
formatter.Serialize(stream, obj);
stream.Position = 0;
obj = null;
obj = (MyObject)formatter.Deserialize(stream);
obj.Write();
Console.Read();
}
}
}
```
结果为:
![](https://box.kancloud.cn/2016-01-23_56a2eb3aa7d30.png)
此时的执行过程为:当格式化器序列化对象时,会检查每个对象,如果发现一个对象的类型实现了**ISerializable**接口,格式化器会忽视所有定制属性,改为构造一个新的**System.Runtime.Serialization.SerializationInfo**对象,这个对象包含了要实际为对象序列化的值的集合。构造好并初始化好SerializationInfo对象后,格式化器调用类型的**GetObjectData**方法,并向它传递对**SerializationInfo**对象的引用,**GetObjectData**方法负责决定需要哪些信息来序列化对象,并将这些信息添加到**SerializationInfo**对象中,通过调用AddValue方法来添加需要的每个数据,添加好所有必要的序列化信息后,会返回至格式化器,然后格式化器获取已经添加到**SerializationInfo**对象中的所有值,并将它们都序列化到流中,当反序列化时,格式化器从流中提取一个对象时,会为新对象分配内存,最初,这个对象的所有字段都设为0或null,然后,格式化器检查类型是否实现了**ISerializable**接口,如果存在这个接口, 格式化器就尝试调用一个特殊构造器,它的参数和**GetObjectData**方法的完全一致。
## 四、格式化器如何序列化和反序列化
从上面的分析中可以看出,进行序列化和反序列化主要是格式化器在工作的,然而下面就是要讲讲格式化器是如何序列化一个应用了 **SerializableAttribute** 属性的对象。
1. 格式化器调用**FormatterServices**的**GetSerializableMembers**方法:public static MemberInfo[] GetSerializableMembers(Type type,StreamingContext context);这个方法利用发射获取类型的public和private实现字段(标记了**NonSerializedAttributee**属性的字段除外)。方法返回由MemberInfo对象构成的一个数组,其中每个元素对应于一个可序列化的实例字段。
2. 对象被序列化,**System.Reflection.MemberInfo**对象数组传给**FormatterServices**的静态方法**GetObjectData**: public static object[] GetObjectData(Object obj,MemberInfo[] members); 这个方法返回一个**Object**数组,其中每个元素都标识了被序列化的那个对象中的一个字段的值。
3. 格式化器将程序集标识和类型的完整名称写入流中。
4. 格式化器然后遍历两个数组中的元素,将每个成员的名称和值写入流中。
接下来是解释格式化器如何自动反序列化一个应用了 **SerializableAttribute**属性的对象。
1. 格式化器从流中读取程序集标识和完整类型名称。
2. 格式化器调用**FormatterServices**的静态方法**GetUninitializedObject**: public static Object GetUninitializedObject(Type ttype);这个方法为一个新对象分配内存,但不为对象调用构造器。然而,对象的所有字段都被初始化为0或null.
3. 格式化器现在构造并初始化一个**MemberInfo**数组,调用**FormatterServices**的**GetSerializableMembers**方法,这个方法返回序列化好、现在需要反序列化的一组字段。
4. 格式化器根据流中包含的数据创建并初始化一个**Object**数组。
5. 将对新分配的对象、**MemberInfo**数组以及并行**Object**数组的引用传给**FormatterServices**的静态方法**PopulateObjectMembers**:
public static Object PopulateObjectMembers(Object obj,MemberInfo[] members,Object[] data);这个方法遍历数组,将每个字段初始化成对应的值。
注:格式化如何序列化和反序列对象部分摘自CLR via C#(第三版),写在这里可以让初学者进一步理解格式化器在序列化和反序列化过程中所做的工作。
写到这里这篇关于序列化和反序列的文章终于结束了, 希望对自己以后复习和园子里的朋友有帮助。
- C# 基础知识系列
- C# 基础知识系列 专题一:深入解析委托——C#中为什么要引入委托
- C# 基础知识系列 专题二:委托的本质论
- C# 基础知识系列 专题三:如何用委托包装多个方法——委托链
- C# 基础知识系列 专题四:事件揭秘
- C# 基础知识系列 专题五:当点击按钮时触发Click事件背后发生的事情
- C# 基础知识系列 专题六:泛型基础篇——为什么引入泛型
- C# 基础知识系列 专题七: 泛型深入理解(一)
- C# 基础知识系列 专题八: 深入理解泛型(二)
- C# 基础知识系列 专题九: 深入理解泛型可变性
- C#基础知识系列 专题十:全面解析可空类型
- C# 基础知识系列 专题十一:匿名方法解析
- C#基础知识系列 专题十二:迭代器
- C#基础知识 专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
- C# 基础知识系列 专题十四:深入理解Lambda表达式
- C# 基础知识系列 专题十五:全面解析扩展方法
- C# 基础知识系列 专题十六:Linq介绍
- C#基础知识系列 专题十七:深入理解动态类型
- 你必须知道的异步编程 C# 5.0 新特性——Async和Await使异步编程更简单
- 全面解析C#中参数传递
- C#基础知识系列 全面解析C#中静态与非静态
- C# 基础知识系列 C#中易混淆的知识点
- C#进阶系列
- C#进阶系列 专题一:深入解析深拷贝和浅拷贝
- C#进阶系列 专题二:你知道Dictionary查找速度为什么快吗?
- C# 开发技巧系列
- C# 开发技巧系列 使用C#操作Word和Excel程序
- C# 开发技巧系列 使用C#操作幻灯片
- C# 开发技巧系列 如何动态设置屏幕分辨率
- C# 开发技巧系列 C#如何实现图片查看器
- C# 开发技巧 如何防止程序多次运行
- C# 开发技巧 实现属于自己的截图工具
- C# 开发技巧 如何使不符合要求的元素等于离它最近的一个元素
- C# 线程处理系列
- C# 线程处理系列 专题一:线程基础
- C# 线程处理系列 专题二:线程池中的工作者线程
- C# 线程处理系列 专题三:线程池中的I/O线程
- C# 线程处理系列 专题四:线程同步
- C# 线程处理系列 专题五:线程同步——事件构造
- C# 线程处理系列 专题六:线程同步——信号量和互斥体
- C# 多线程处理系列专题七——对多线程的补充
- C#网络编程系列
- C# 网络编程系列 专题一:网络协议简介
- C# 网络编程系列 专题二:HTTP协议详解
- C# 网络编程系列 专题三:自定义Web服务器
- C# 网络编程系列 专题四:自定义Web浏览器
- C# 网络编程系列 专题五:TCP编程
- C# 网络编程系列 专题六:UDP编程
- C# 网络编程系列 专题七:UDP编程补充——UDP广播程序的实现
- C# 网络编程系列 专题八:P2P编程
- C# 网络编程系列 专题九:实现类似QQ的即时通信程序
- C# 网络编程系列 专题十:实现简单的邮件收发器
- C# 网络编程系列 专题十一:实现一个基于FTP协议的程序——文件上传下载器
- C# 网络编程系列 专题十二:实现一个简单的FTP服务器
- C# 互操作性入门系列
- C# 互操作性入门系列(一):C#中互操作性介绍
- C# 互操作性入门系列(二):使用平台调用调用Win32 函数
- C# 互操作性入门系列(三):平台调用中的数据封送处理
- C# 互操作性入门系列(四):在C# 中调用COM组件
- CLR
- 谈谈: String 和StringBuilder区别和选择
- 谈谈:程序集加载和反射
- 利用反射获得委托和事件以及创建委托实例和添加事件处理程序
- 谈谈:.Net中的序列化和反序列化
- C#设计模式
- UML类图符号 各种关系说明以及举例
- C#设计模式(1)——单例模式
- C#设计模式(2)——简单工厂模式
- C#设计模式(3)——工厂方法模式
- C#设计模式(4)——抽象工厂模式
- C#设计模式(5)——建造者模式(Builder Pattern)
- C#设计模式(6)——原型模式(Prototype Pattern)
- C#设计模式(7)——适配器模式(Adapter Pattern)
- C#设计模式(8)——桥接模式(Bridge Pattern)
- C#设计模式(9)——装饰者模式(Decorator Pattern)
- C#设计模式(10)——组合模式(Composite Pattern)
- C#设计模式(11)——外观模式(Facade Pattern)
- C#设计模式(12)——享元模式(Flyweight Pattern)
- C#设计模式(13)——代理模式(Proxy Pattern)
- C#设计模式(14)——模板方法模式(Template Method)
- C#设计模式(15)——命令模式(Command Pattern)
- C#设计模式(16)——迭代器模式(Iterator Pattern)
- C#设计模式(17)——观察者模式(Observer Pattern)
- C#设计模式(18)——中介者模式(Mediator Pattern)
- C#设计模式(19)——状态者模式(State Pattern)
- C#设计模式(20)——策略者模式(Stragety Pattern)
- C#设计模式(21)——责任链模式
- C#设计模式(22)——访问者模式(Vistor Pattern)
- C#设计模式(23)——备忘录模式(Memento Pattern)
- C#设计模式总结
- WPF快速入门系列
- WPF快速入门系列(1)——WPF布局概览
- WPF快速入门系列(2)——深入解析依赖属性
- WPF快速入门系列(3)——深入解析WPF事件机制
- WPF快速入门系列(4)——深入解析WPF绑定
- WPF快速入门系列(5)——深入解析WPF命令
- WPF快速入门系列(6)——WPF资源和样式
- WPF快速入门系列(7)——深入解析WPF模板
- WPF快速入门系列(8)——MVVM快速入门
- WPF快速入门系列(9)——WPF任务管理工具实现
- ASP.NET 开发
- ASP.NET 开发必备知识点(1):如何让Asp.net网站运行在自定义的Web服务器上
- ASP.NET 开发必备知识点(2):那些年追过的ASP.NET权限管理
- ASP.NET中实现回调
- 跟我一起学WCF
- 跟我一起学WCF(1)——MSMQ消息队列
- 跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(4)——第一个WCF程序
- 跟我一起学WCF(5)——深入解析服务契约 上篇
- 跟我一起学WCF(6)——深入解析服务契约 下篇
- 跟我一起学WCF(7)——WCF数据契约与序列化详解
- 跟我一起学WCF(8)——WCF中Session、实例管理详解
- 跟我一起学WCF(9)——WCF回调操作的实现
- 跟我一起学WCF(10)——WCF中事务处理
- 跟我一起学WCF(11)——WCF中队列服务详解
- 跟我一起学WCF(12)——WCF中Rest服务入门
- 跟我一起学WCF(13)——WCF系列总结
- .NET领域驱动设计实战系列
- .NET领域驱动设计实战系列 专题一:前期准备之EF CodeFirst
- .NET领域驱动设计实战系列 专题二:结合领域驱动设计的面向服务架构来搭建网上书店
- .NET领域驱动设计实战系列 专题三:前期准备之规约模式(Specification Pattern)
- .NET领域驱动设计实战系列 专题四:前期准备之工作单元模式(Unit Of Work)
- .NET领域驱动设计实战系列 专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
- .NET领域驱动设计实战系列 专题六:DDD实践案例:网上书店订单功能的实现
- .NET领域驱动设计实战系列 专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
- .NET领域驱动设计实战系列 专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现
- .NET领域驱动设计实战系列 专题九:DDD案例:网上书店AOP和站点地图的实现
- .NET领域驱动设计实战系列 专题十:DDD扩展内容:全面剖析CQRS模式实现
- .NET领域驱动设计实战系列 专题十一:.NET 领域驱动设计实战系列总结