目录结构:

contents structure [+]

在这边文章中,笔者将会将会详细阐述C#中的序列化和反序列,希望可以对你有所帮助。

1.简介

众所周知,序列化是将对象或对象图转化字节流的过程,反序列化是将字节流转化为对象图的过程。
如果要使一个类型可序列化的话,必需向类型应用定制特性System.SerializableAttribute。请注意,SerializableAttribute特性只能应用于引用类型(class)、值类型(struct)、枚举类型(enum)和委托类型(delegate)。枚举和委托类型总是可序列化的所以不必显示使用SerializableAttribute特性。

序列化必须要使用到序列化器,它用于完成将数据转化为特定格式的数据。以下列举四种格式化器:
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter 以二进制的格式对对象进行序列化和反序列操作。

System.Runtime.Serialization.Formatters.Soap.SoapFormatter 以SOAP的格式对对象进行序列化和反序列化操作,从.NET Framework2.0开始,该类就废弃,推荐使用BinaryFormatter类。

System.Runtime.Serialization.NetDataContractSerializer 用.NET Framework提供的类型,将类型实例序列化和返序列化为XML流或文档结构。

System.Runtime.Serialization.DataContractSerializer 使用指定的数据协定,将类型实例序列化和反序列化XML流或文档结构

System.Xml.Serialization.XmlSerializer 将类型的实例序列化和反序列化XML文档,该类允许控制如何将对象编码为XML文档。

2.控制序列化和反序列化

如果将SerializableAttribute特性应用于某个类型,那么标志该类型的实例可以进行序列化和反序列化操作,该类型实例的所有数据都可以进行序列化和反序列化操作,如果需要更精准的控制序列化和反序列化的数据,那么就需要控制序列化和反序列化的过程了。

这里笔者把序列化和反序列化的操作方式分为两种,分别为通过特性和通过接口的方式。

2.1 特性(OnSerializing、OnSerialized、OnDeserializing、OnDeserialized、NonSerialized...)

在这里笔者将会介绍序列化中常用的特性,用这些特性可以控制序列化的过程。

System.Runtime.Serialization.OnSerializingAttribute:
应用OnSerializingAttribute特性的方法,将会在序列化期间被调用。同时,应用OnSerializingAttribute特性的方法必须包含一个System.Runtime.Serialization.StreamingContext参数。

System.Runtime.Serialization.OnSerializedAttribute:
应用OnSerializedAttribute特性的方法,将会在序列化之后被调用。同时,应用OnSerializedAttribute特性的方法必须包含一个System.Runtime.Serialization.StreamingContext参数。

System.Runtime.Serialization.OnDeserializingAttribute:
应用OnDeserializingAttribute特性的方法,将会在被序列化期间被调用。同时,应用OnDeserializingAttribute特性的方法必须包含一个System.Runtime.Serialization.StreamingContext参数。

System.Runtime.Serialization.OnDeserializedAttribute:
应用OnDeserializedAttribute特性的方法,将会在被序列化之后调用。同时,应用OnDeserializedAttribute特性的方法必须包含一个System.Runtime.Serialization.StreamingContext参数。

System.NonSerializedAttribute:
应用NonSerializedAttribute特性的字段,将会不会序列化。可以利用这个特性保护保护敏感数据,NonSerializedAttribute不仅可以引用字段,还以应用于event。

例如:

[field:NonSerializedAttribute()]
public event ChangedEventHandler Changed;

下面给上面使用上面特性的类:

//标记为可序列化
[Serializable]
class MyType {
Int32 x, y;
//标记num为不可序列
[NonSerialized]
Int32 num; public MyType(Int32 x, Int32 y) {
this.x = x;
this.y = y;
this.num=(x+y);
} //标记该方法在序列化期间被调用
[OnSerializing]
private void OnSerializing(StreamingContext context) {
//举例:在序列化前,修改任何需要修改的状态
} //标记该方法在序列化之后被调用
[OnSerialized]
private void OnSerialized(StreamingContext context) {
//举例:在序列化之后,恢复任何需要恢复的状态
} //标记该方法在反序列化期间被调用
[OnDeserializing]
private void OnDeserialing(StreamingContext context) {
//举例:在反序列化期间,为字段设置默认值
} //标记该方法在反序列化之后被调用
[OnDeserialized]
private void OnDeserialized(StreamingContext context) {
//举例:根据字段值初始化瞬间状态(比如num值)
num = x + y;
}
}

2.2 接口(ISerializable)

在前面已经介绍过通过OnSerializing,OnSerialized,OnDeserializing,OnDeserialized等特性。除了使用特性,还可以让类型实现System.Runtime.Serialization.ISerializable接口。
该接口的定义如下:

public interface ISerializable{
void GetObjectData(SerializationInfo info,StreamingContext context);
}

实现ISerializable接口,除了需要实现GetObjectData方法,还应该提供一个特殊的构造器。

注意:
ISerializable接口和特殊构造器旨在由格式化器使用,但其他代码可能调用GetObjectData来返回敏感数据,或传入损坏的数据。建议向GetObjectData方法和特殊构造器应用以下特性:
[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter=true)]

如果在类型中必须访问提取对象中的成员,建议类型提供一个OnDeserialized特性或是实现IDeseializationCallback接口的OnDeserialization方法。调用该方法时,所有对象的字段都已经初始化好了。

    class Program
{
static void Main(string[] args)
{
MyItemType myItermType = new MyItemType("hello");
using(MemoryStream memoryStream = new MemoryStream()){
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(memoryStream, myItermType); memoryStream.Position = ;
myItermType = null; myItermType = (MyItemType)binaryFormatter.Deserialize(memoryStream);
Console.WriteLine(myItermType.MyProperty);//hello
} Console.ReadLine();
}
}
[Serializable]
public class MyItemType : ISerializable,IDeserializationCallback
{
private string myProperty_value;
[NonSerialized]
private SerializationInfo m_info = null; public MyItemType(String property)
{
this.myProperty_value = property;
} public string MyProperty
{
get { return myProperty_value; }
set { myProperty_value = value; }
} //在序列化期间被调用
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("props", myProperty_value, typeof(string));
} //在反序列化期间被调用
public MyItemType(SerializationInfo info, StreamingContext context)
{
//将SerializationInfo的引用保留下来。
//之所以不在构造方法中完成字段赋值,是因为如果要访问当前对象的成员(方法),那么此时成员很有可能没有初始化完成,可能出现不可预期的结果
m_info = info;
} //在反序列化之后调用
public void OnDeserialization(object sender)
{
myProperty_value = (string)m_info.GetValue("props", typeof(string));
}
}

在这里,在上面我们知道了SerializationInfo对象其中一个重要的方法就是AddValue,使用该方法可以将对象添加到序列化的过程中。SerializationInfo除了AddValue,还有一个值得说明的方法就是setType,使用这个方法可以设置序列化的数据类型,如果恰好该类型实现了IObjectReference接口的话,将会在反序列化之后,自动调用其抽象方法:
IObjectReference接口原型为:

public interface IObjectReference{
Object GetRealObject(StreamingContext context);
}

看如下如何序列化和反序列化单实例的栗子:

    [Serializable]
public sealed class Singleton : ISerializable { //该类型的实例
private static readonly Singleton s_theOneObject = new Singleton(); //实例字段
public String Name = "Jeff";
public DateTime Date = DateTime.Now; //私有构造器,只允许这个类型构造单实例
private Singleton() { } //返回对该单实例的引用
public static Singleton GetSingleton() {
return s_theOneObject;
} //序列化一个Singleton时调用
[SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
void ISerializable.GetObjectData(SerializationInfo info,StreamingContext context) {
info.SetType(typeof(SingletonSerializationHelper));
} [Serializable]
private sealed class SingletonSerializationHelper : IObjectReference {
//该方法在对象反序列化之后调用
public Object GetRealObject(StreamingContext context) {
return Singleton.GetSingleton();
}
} //注意:特殊构造器不需要,因为它永远都不会被调用
}

测试代码为:

        static void Main(string[] args)
{
Singleton[] a1 = { Singleton.GetSingleton(),Singleton.GetSingleton()}; Console.WriteLine(a1[]==a1[]);//true using(var stream=new MemoryStream()){
BinaryFormatter formatter = new BinaryFormatter(); //先序列化再反序列化
formatter.Serialize(stream,a1);
stream.Position = ; Singleton[] a2 = (Singleton[])formatter.Deserialize(stream); Console.WriteLine(a2[]==a2[]);//true
Console.WriteLine(a1[] == a1[]);//true;
}
Console.ReadLine();
}

3.流上下文(StreamingContext)

一组序列化好的对象有许多用处;同一个进程,同一台机器上的不同进程、不同机器上的不同进程。在一些比较少见的情况下,一个对象可能想知道它要在什么地方被反序列化,从而以不同的方式生成其形态。例如,如果对象中包装了Windows信号量(semaphone)对象,如果它知道要反序列化到同一个进程中,就可决定对它的内核句柄(kernal handle)进行序列化,这是因为内核句柄在同一个进程中有限。但如果要反序列化到同一台机器的不同进程中,那么可以决定对信号量的字符串名称序列化。最后,如果要反序列化到不同机器上,那么就可决定抛出异常,因为信号量只在同一台机器上有效。

SteamingContext有两个公共只读属性,如下所示:

Sate StreamingContextStates 一组位标志,指定要序列化/反序列化的对象的来源或目的地
Context Object 一个对象引用,对象中包含用户希望的任何上下文信息

通过State属性,就可判断序列化/反序列化的来源或目的地。

StreamingContextStates的标志:

标志说明 标志值 说明
CrossProcess 0x0001 来源或目的地是同一台机器的不同进程
CrossMachines 0x0002 来源或目的地在不同机器上
File 0x0004 来源或目的地是文件。不保证反序列化数据是同一个进程
Persistence 0x0008 来源或目的地是存储(store),比如数据库或文件。不保证反序列化数据的是同一个进程
Remoting 0x0010 来源或目的地是远程的未知未知。这个位置可能在(也可能不在)同一台机器上。
Other 0x0020 来源或目的地未知
Clone 0x0040 对象图被克隆。序列化代码可认为是由同一进程对数据进行反序列化,所以可安全地访问句柄或其他非托管设备。
CrossAppDomain 0x0080 来源或目的地是不通过的AppDomain
All 0x00FF 来源或目的地可能是上述任何一个上下文。这是默认设定

知道了如何获取这些信息后,接下来进行设置这些信息。在IFormatter接口(BinaryFormatter和SoapFormtter类型均实现该接口)定义了StreamingContext的可读/可写属性Context,构造格式化器时候,格式化器会初始化它的Context属性,将StreamingContextStates状态设置为All,将其对额外状态对象的引用设置为null。
接下来举如下栗子:

        private static Object DeepClone(Object original) {
using(MemoryStream stream=new MemoryStream()){
BinaryFormatter formatter = new BinaryFormatter(); formatter.Context = new StreamingContext(StreamingContextStates.Clone); formatter.Serialize(stream,original); //定义到内存流的起始位置
stream.Position = ; return formatter.Deserialize(stream);
}
}

4.序列化代理

前面介绍了如何修改一个类型的实现,控制该类型如何对它本身的实例进行序列化和反序列化。然而,格式化器还允许不是“类型实现的一部分”的代码重写该类型“序列化和反序列化其对象”。这就是序列化代理。
要使这个机子工作起来,需要按照如下步骤:
a.首先要定义一个“代理类型”,它接管对现有类型的序列化和反序列化活动
b.向格式化器登记注册这个代理类型的实例,并告诉格式化器代理要作为的类型是什么。
c.一旦格式化器序列化/反序列化这个类型,那么将会调用由关联的代理类型关联的方法。

代理类型必须实现System.Runtime.Serialization.ISerializationSurrogate接口,该接口定义如下:

public interface ISerializationSurrogate{
void GetObjectDate(Object obj,SerializationInfo info,StreamingContext context);
Object SetObjectDate(Object obj,SerializationInfo info,StreamingContext context,ISurrogateSelector selector);
}

其中GetObjectDate在序列化时调用,SetObjectDate在反序列化时调用。

下面的栗子展示了如何使用代理类:

    internal sealed class UniversalToLocalTimeSerializationSurrogate : ISerializationSurrogate {
public void GetObjectData(object obj,SerializationInfo info,StreamingContext context) {
//将DateTime从本地时间转化为UTC
info.AddValue("date", ((DateTime)obj).ToUniversalTime().ToString("u"));
} public object SetObjectData(object obj,SerializationInfo info,StreamingContext context,ISurrogateSelector selector) {
//将Datetime从UTC转化为本地时间
return DateTime.ParseExact(info.GetString("date"),"u",null).ToLocalTime();
}
}

测试代码如下:

       static void Main(string[] args)
{
using(var stream=new MemoryStream()){
//构造格式化器
IFormatter formatter = new BinaryFormatter(); //构造SurrogateSelector(代理选择器)对象
SurrogateSelector ss = new SurrogateSelector();
SurrogateSelector ss2 = new SurrogateSelector();
ss.ChainSelector(ss2); //告诉代理选择器为DateTime对象使用指定的代理对象
ss.AddSurrogate(typeof(DateTime),formatter.Context,new UniversalToLocalTimeSerializationSurrogate());
//注意:addSurrogate可多次调用来登记代理 //告诉格式化器使用代理选择器
formatter.SurrogateSelector = ss; //创建一个DateTime对象代表本地时间,并序列化它
DateTime localTimeBeforeSerialize = DateTime.Now;
formatter.Serialize(stream,localTimeBeforeSerialize); //stream将Universal时间作为一个字符串显示,证明序列化成功
stream.Position = ;
Console.WriteLine(new StreamReader(stream).ReadToEnd()); //反序列化Universal字符串
stream.Position = ;
DateTime localTimeAfterDeserialize = (DateTime)formatter.Deserialize(stream); Console.WriteLine("DateTimeBeforSerialize={0}", localTimeBeforeSerialize);//DateTimeAfterSerialize=2018/8/26 16:42:14
Console.WriteLine("DateTimeAfterSerialize={0}", localTimeAfterDeserialize);//DateTimeAfterSerialize=2018/8/26 16:42:14 Console.ReadLine();
}
}

【C#】详解C#序列化的更多相关文章

  1. Java 序列化Serializable详解

    Java 序列化Serializable详解(附详细例子) Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化Serialization(序列化)是一种将对象以一连 ...

  2. php 序列化(serialize)格式详解

    1.前言 PHP (从 PHP 3.05 开始)为保存对象提供了一组序列化和反序列化的函数:serialize.unserialize.不过在 PHP 手册中对这两个函数的说明仅限于如何使用,而对序列 ...

  3. 转载 C# 序列化与反序列化意义详解

    C# 序列化与反序列化意义详解 总结: ①序列化基本是指把一个对象保存到文件或流中,比如可以把文件序列化以保存到Xml中,或一个磁盘文件中②序列化以某种存储形式使自定义对象持久化: ③将对象从一个地方 ...

  4. Java 序列化Serializable详解(附详细例子)

    Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化 Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization ...

  5. 通讯协议序列化解读(一) Protobuf详解教程

    前言:说到JSON可能大家很熟悉,是目前应用最广泛的一种序列化格式,它使用起来简单方便,而且拥有超高的可读性.但是在越来越多的应用场景里,JSON冗长的缺点导致它并不是一种最优的选择. 一.常用序列化 ...

  6. Newtonsoft.Json C# Json序列化和反序列化工具的使用、类型方法大全 C# 算法题系列(二) 各位相加、整数反转、回文数、罗马数字转整数 C# 算法题系列(一) 两数之和、无重复字符的最长子串 DateTime Tips c#发送邮件,可发送多个附件 MVC图片上传详解

    Newtonsoft.Json C# Json序列化和反序列化工具的使用.类型方法大全   Newtonsoft.Json Newtonsoft.Json 是.Net平台操作Json的工具,他的介绍就 ...

  7. java 序列化Serializable 详解

    Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization是 ...

  8. C# XML序列化与反序列化与XML格式详解

    1.https://www.cnblogs.com/sandyliu1999/p/4844664.html XML是有层次结构的,序列化实际就是内存化,用连续的结构化的内存来存储表示一个对象,那么这两 ...

  9. Java 序列化Serializable详解(附详细例子)

    Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization是 ...

随机推荐

  1. HashTable、HashMap、ConcurrentHashMap的区别

    HashTable是做了同步的,HashMap未考虑同步.所以HashMap在单线程情况下效率较高:HashTable在的多线程情况下,同步操作能保证程序执行的正确性. HashMap是非线程安全的, ...

  2. LeetCode 4. Median of Two Sorted Arrays (分治)

    两个有序的数组 nums1 和 nums2 维数分别为m,n.找所有数的中位数,复杂度 O(log (m+n)) 注意:奇偶个数,分治法求解,递归出口特殊处理.取Kth smallest数时,分治取m ...

  3. la 4015

    题解: 烂大街的树形dp?? f[i][j]表示到i点,在i的子树中经过j个,且要返回i点的最小值 g[i][j]表示到i点,在i的子树中经过j个,且不用返回i点的最小值 然后转移做背包就可以了 (注 ...

  4. Python open详解

    一.打开文件的模式有: 1.r,只读模式[默认]. 2.w,只写模式.[不可读,不存在则创建,存在则删除内容] 3.a,追加模式.[可读,不存在则创建,存在则只追加内容] 二.+ 表示可以同时读写某个 ...

  5. BZOJ1088 [SCOI2005]扫雷Mine 动态规划

    欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目传送门 - BZOJ1088 题意概括 扫雷.只有2行.第2行没有雷,第一行有雷.告诉你第二行显示的数组,问有几种摆放方式 ...

  6. Nginx禁止直接通过IP地址访问网站以及限制IP登陆某目录(关闭默认站点或空主机头)

    这篇文章主要介绍了Nginx中禁止使用IP访问网站的配置实例,一般在备案时可能需要这种设置,需要的朋友可以参考下   国内因为备案的原因,所有服务器都要禁止使用IP访问网站.否则,如果允许使用IP访问 ...

  7. PHP 扩展 MongoDB

    打开phpinfo 查看 nts(非线程) 还是 ts (线程),操作位数: 下载对应的版本的php_mongodb.dll 文件 下载链接: pecl mangodb下载 把文件解压出来 php_m ...

  8. pyrhon SQLite数据库

    pyrhon SQLite数据库 目录 介绍 导入模块 创建数据库/打开数据库 创建表 在表中插入行 查询/修改 删除表中的行 删除表 介绍 Python SQLITE数据库是一款非常小巧的嵌入式开源 ...

  9. web扫描工具-Nikto介绍与使用

    Nikto Perl语言开发的开源Web安全扫描器 web扫描模式:截断代理主动扫描 可以扫描的方面:软件版本搜索存在安全隐患的文件服务器配置漏洞WEB Application层面的安全隐患避免404 ...

  10. 两类传输协议:TCP,UDP

    1) TCP是Transfer Control Protocol的简称,是一种面向连接的保证可靠传输的协议.通过TCP协议传输,得到的是一个顺序的无差错的数据流.发送方和接收方的成对的两个socket ...