【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是 ...
随机推荐
- POJ 2377 Bad Cowtractors (Kruskal)
题意:给出一个图,求出其中的最大生成树= =如果无法产生树,输出-1. 思路:将边权降序再Kruskal,再检查一下是否只有一棵树即可,即根节点只有一个 #include <cstdio> ...
- linux命令简写解释
命令缩写: ls:list(列出目录内容) cd:Change Directory(改变目录) su:switch user 切换用户rpm:redhat package manager 红帽子打包管 ...
- 《Gradle权威指南》--Gradle插件
No1: 应用插件 apply plugin:'java' apply plugin:org.gradle.api.plugins.JavaPlugin apply plugin:JavaPlugin ...
- css实现幻灯片播放效果
用css实现幻灯片播放是最基础的,闲下来没事就试着写了一下,如果有不够完善或者方法不好的地方还请指点.下面我就用两种方法实现css花灯片效果. 方法1:定位.通过position属性改变left值 h ...
- 002.Open-Falcon Server部署及Agent监控
一 前期准备 节点 IP 备注 falcon 私网:172.24.10.95 临时公网:120.132.23.107 Open-Falcon服务端 node01 172.24.10.216 被监控端 ...
- codeforces-1102e
https://www.cnblogs.com/31415926535x/p/10439313.html 这道题很锻炼思维,,是到好题,,就是我不在状态,,没看出来线段间的关系,,学会了求一个区间里相 ...
- dom那些事儿
一.dom常识1.style属性style对象的属性值都是字符串,设置时必须包括单位,但是不含规则结尾的分号.比如,elem.style.width不能写为100,而要写为100px. 2.getCo ...
- JSONObject基本内容(三)
参考资料:http://swiftlet.net/archives/category/json 十分感谢!!!~~ 第三篇的内容,主要讲述的有两点: 1 .如何获取JSONObject中对应ke ...
- 论文--Topic-Sensitive PageRank
背景 原有的PageRank方法:通过web上链接结构信息得到页面之间相对的重要性,和特定的查询内容无关 论文涉及到的其他算法 HITS Hilltop,处理常见的流行查询 popular query ...
- 潭州课堂25班:Ph201805201 爬虫基础 第四课 Requests (课堂笔记)
优雅到骨子里的Requests 1528811134432 简介 上一篇文章介绍了Python的网络请求库urllib和urllib3的使用方法,那么,作为同样是网络请求库的Request ...