目录结构:

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. [转] webpack3.0踩坑:postcss-loader的使用

    解决方案: 只是换了一种引入方式,解决了 1,创建postcss.config.js文件,添加如下代码:(引入autoprefixer插件)   1 2 3 4 module.exports = {  ...

  2. LYK loves graph(graph)

    题目: LYK loves graph(graph) Time Limit:2000ms   Memory Limit:128MB LYK喜欢花花绿绿的图片,有一天它得到了一张彩色图片,这张图片可以看 ...

  3. Nginx 关键字详解

    转自: https://blog.csdn.net/zhangliangzi/article/details/78257593 1.[alias]——别名配置,用于访问文件系统,在匹配到locatio ...

  4. python开发环境PyCharm安转注册

    0x1 ,安装 0x2 , 调整时间到2038年. 0x3 ,申请30天试用 0x4, 退出pycharm 0x5, 时间调整回来. ##注册方法2### 注册方法:在注册时选择 License se ...

  5. coalesce :返回参数(列名)中第一个非NULL值的字段值

    示例 下面的语句返回值 34.(只返回一个值就算后面不为NULL也直接丢弃) SELECT COALESCE( NULL, 34, 13, 0 ) 备注 如果所有参数均为 NULL,则 COALESC ...

  6. Linux学习之文件属性chattr权限与sudo权限(十二)

    Linux学习之文件属性chattr权限与sudo权限 文件属性chattr Linux文件的隐藏属性在保护系统文件的安全性上非常重要,是防止误操作的,对root用户也同样有效.chattr命令只能在 ...

  7. Python Django 学习 (二) 【Django 模型】

    注: 由于自己排版确实很难看,本文开始使用markdown编辑,希望有所改善 官方定义 A model is the single, definitive source of information ...

  8. iOS开发安全

    这篇文章之前自己在公司的技术分享学院发表了.现在发到自己的博客上. 现在很多iOS的app没有做任何的安全防范措施.今天我们就聊聊iOS开发人员平时怎么做才更安全. 一.网络方面 用抓包工具可以抓取手 ...

  9. gevent实现异步

    # coding:utf-8 import gevent import requests from gevent import monkey # 猴子补丁就是在运行时对代码进行修改 # 由于IO操作非 ...

  10. HDU 5628 Clarke and math——卷积,dp,组合

    HDU 5628 Clarke and math 本文属于一个总结了一堆做法的玩意...... 题目 简单的一个式子:给定$n,k,f(i)$,求 然后数据范围不重要,重要的是如何优化这个做法. 这个 ...