【C#】详解C#序列化
目录结构:
在这边文章中,笔者将会将会详细阐述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#序列化的更多相关文章
- Java 序列化Serializable详解
Java 序列化Serializable详解(附详细例子) Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化Serialization(序列化)是一种将对象以一连 ...
- php 序列化(serialize)格式详解
1.前言 PHP (从 PHP 3.05 开始)为保存对象提供了一组序列化和反序列化的函数:serialize.unserialize.不过在 PHP 手册中对这两个函数的说明仅限于如何使用,而对序列 ...
- 转载 C# 序列化与反序列化意义详解
C# 序列化与反序列化意义详解 总结: ①序列化基本是指把一个对象保存到文件或流中,比如可以把文件序列化以保存到Xml中,或一个磁盘文件中②序列化以某种存储形式使自定义对象持久化: ③将对象从一个地方 ...
- Java 序列化Serializable详解(附详细例子)
Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化 Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization ...
- 通讯协议序列化解读(一) Protobuf详解教程
前言:说到JSON可能大家很熟悉,是目前应用最广泛的一种序列化格式,它使用起来简单方便,而且拥有超高的可读性.但是在越来越多的应用场景里,JSON冗长的缺点导致它并不是一种最优的选择. 一.常用序列化 ...
- Newtonsoft.Json C# Json序列化和反序列化工具的使用、类型方法大全 C# 算法题系列(二) 各位相加、整数反转、回文数、罗马数字转整数 C# 算法题系列(一) 两数之和、无重复字符的最长子串 DateTime Tips c#发送邮件,可发送多个附件 MVC图片上传详解
Newtonsoft.Json C# Json序列化和反序列化工具的使用.类型方法大全 Newtonsoft.Json Newtonsoft.Json 是.Net平台操作Json的工具,他的介绍就 ...
- java 序列化Serializable 详解
Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization是 ...
- C# XML序列化与反序列化与XML格式详解
1.https://www.cnblogs.com/sandyliu1999/p/4844664.html XML是有层次结构的,序列化实际就是内存化,用连续的结构化的内存来存储表示一个对象,那么这两 ...
- Java 序列化Serializable详解(附详细例子)
Java 序列化Serializable详解(附详细例子) 1.什么是序列化和反序列化Serialization(序列化)是一种将对象以一连串的字节描述的过程:反序列化deserialization是 ...
随机推荐
- bozj 1823(未完成)
题意: 满汉全席是中国最丰盛的宴客菜肴,有许多种不同的材料透过满族或是汉族的料理方式,呈现在數量繁多的菜色之中.由于菜色众多而繁杂,只有极少數博学多闻技艺高超的厨师能够做出满汉全席,而能够烹饪出经过专 ...
- BZOJ1588 [HNOI2002]营业额统计 set
欢迎访问~原文出处——博客园-zhouzhendong 去博客园看该题解 题目传送门 - BZOJ1588 题意概括 给出数列,求 ∑F[i],其中F[1] = a[1] , F[i] = min( ...
- 利用python将表格中的汉字转化为拼音
缺少包时用pip install 进行安装,例如: pip install xlsxwriter 完成代码如下: #!/usr/bin/python #-*-coding:utf-8-*- #fr ...
- python开发环境PyCharm安转注册
0x1 ,安装 0x2 , 调整时间到2038年. 0x3 ,申请30天试用 0x4, 退出pycharm 0x5, 时间调整回来. ##注册方法2### 注册方法:在注册时选择 License se ...
- 通过pyqt5实现俄罗斯方块游戏例子
# *_* coding:utf-8 *_* # 开发团队:中国软件开发团队# 开发人员:Administrator# 开发时间:2019/3/17 2:13# 文件名称:RussiaBoard# 开 ...
- 在phpstorm中svn的使用
目 录 1.搭建svn环境 1.1搭建svn服务端 1.2创建svn用户和密码 2.开始在phpstorm中链接svn 2.1打开Subversion 2.2 输入svn地址 2.3选择导出文件,进 ...
- 浅谈vue之动态路由匹配
在日常开发过程中,可能会遇到一些类似于新闻详情页的内容,需要把所有详情页映射到同一组件上,这是动态路由匹配的应用场景之一.在使用的过程中,也遇到过一些小坑,此篇做个简要的总结说明: 基本使用 { pa ...
- 什么?作为程序员的你还不知道怎么访问 Google
今天就一个目的,让你可以FQ成功,其他人我不知道,但就程序员来说,不能使用 Google 那真是一大损失,当然还有对所有人适用的 YouTobu 这个视频网站,资源多的没话说,别的不说,学习英语很方便 ...
- Nutch源码阅读进程3
走了一遍Inject和Generate,基本了解了nutch在执行爬取前的一些前期预热工作,包括url的过滤.规则化.分值计算以及其与mapreduce的联系紧密性等,自我感觉nutch的整个流程是很 ...
- 洛谷.3807.[模板]卢卡斯定理(Lucas)
题目链接 Lucas定理 日常水题...sublime和C++字体死活不同步怎么办... //想错int范围了...不要被longlong坑 //这个范围现算阶乘比预处理快得多 #include &l ...