c#序列化和反序列化《转载》
(一)使用总体说明
.net framework的类库中提供了三个可以用于序列化和反序列化的类,分别为BinaryFormatter、SoapFormatter和XmlSerializer。
BinaryFormatter的命名空间为System.Runtime.Serialization.Formatters.Binary,位于程序集mscorlib.dll中。
SoapFormatter的命名空间为System.Runtime.Serialization.Formatters.Soap,位于程序集System.Runtime.Serialization.Formatters.Soap.dll中。必须引用System.Runtime.Serialization.Formatters.Soap.dll。
XmlSerializer的命名空间为System.Xml.Serialization,位于程序集System.Xml.dll中。
****非常重要****。需要说明的是,反序列化的过程(从文件-->对象的过程),不是new出来新对象,然后对其进行赋值的。反序列化的时候,不是依据类代码对各个成员进行初步赋值,然后执行构造函数的过程。在反序列化的时候,既不会为成员初赋值,也不会执行构造函数,而是直接对没有标注为[NonSerialized]的字段赋给其保存在文件中的值,而对于标注为[NonSerialized]的字段,其结果仅仅是default(FiledType),此处的FieldType是指字段的类型(注:可以利用OnSerialized方法来事后修改字段的值)。例如:
[Serializable]
public class MyClass
{
public MyClass()
{
//在反序列化的时候,不会执行构造函数
}
private int x=10; //在反序列化的时候,不是将10赋值给x。而是从数据文件中读取相应的值,直接赋值给x。
[NonSerializable]
private int y=15; //在反序列化的时候,不会将15赋值给y。由于y标注了[NonSerializable],因此,反序列化完成的时候,y的值是default(int),也就是0。
private List<string> Strs=new List<string>(); //在反序列化的时候,不会new一个将List<string>并且赋值给Strs。而是从数据文件中读取相应的值,直接赋值给Strs。
[NonSerializable]
private List<string> AnotherStrs=new List<string>(); //在反序列化的时候,不会new一个List<string>并且将其赋值给AnotherStrs。由于AnotherStrs标注了[NonSerializable],因此,反序列化完成的时候,AnotherStrs的值是default(List<string>),也就是null。
}
(二)对三个序列化类的说明
(1)BinaryFormatter
可以对单个对象或集合对象(如List<T>、ObservableCollection<T>)进行序列化。需要指出的是,需要对被序列化的对象添加[Serializable]特性。典型的代码如下:
private void BinarySerialize()
{
BinaryFormatter binFormat = new BinaryFormatter();
string fileName = ...;
using (Stream fStream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))
{
//RecentProjectInfos是ObservableCollection<ProjectInfo>类型的属性
binFormat.Serialize(fStream, RecentProjectInfos);
}
}
private void BinaryDeserialize()
{
BinaryFormatter binFormat = new BinaryFormatter();
string fileName = ...;
using (Stream fStream = File.OpenRead(fileName))
{
ObservableCollection<ProjectInfo> pis = binFormat.Deserialize(fStream) as ObservableCollection<ProjectInfo>;
//RecentProjectInfos是ObservableCollection<ProjectInfo>类型的属性
this.RecentProjectInfos=pis;
}
}
(2)SoapFormatter
可以对单个对象进行序列化。但是,很大的缺陷在于,不能直接对泛型集合数据(如List<T>、ObservableCollection<T>)进行序列化(注:无论是根对象就是泛型集合,还是某个对象下的字段或属性是泛型集合,都不能序列化),而要使用BinaryFormatter或XmlSerializer进行序列化。由于无法对泛型集合对象进行序列化,因此使用面比较窄,个人不建议使用SoapFormatter。
(3)XmlSerializer
无论对于单个对象还是集合对象(如List<T>、ObservableCollection<T>),都可以使用XmlSerializer进行序列化。需要指出的是,不需要对被序列化的对象添加[Serializable]特性注解。但是,使用XmlSeriabizable的时候,被序列化的对象应该具有无参数构造函数。典型的代码如下:
private void XmlSerialize()
{
XmlSerializer binFormat = new XmlSerializer(typeof(ObservableCollection<ProjectInfo>)); //把对象类型作为参数
string fileName = ...;
using (Stream fStream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None))
{
//RecentProjectInfos是ObservableCollection<ProjectInfo>类型的属性
binFormat.Serialize(fStream, RecentProjectInfos);
}
}
private void XmlDeserialize()
{
XmlSerializer binFormat = new XmlSerializer(typeof(ObservableCollection<ProjectInfo>));//把对象类型作为参数
string fileName = ...;
using (Stream fStream = File.OpenRead(fileName))
{
ObservableCollection<ProjectInfo> pis = binFormat.Deserialize(fStream) as ObservableCollection<ProjectInfo>;
//RecentProjectInfos是ObservableCollection<ProjectInfo>类型的属性
this.RecentProjectInfos=pis;
}
}
(三)其它重要事项
(1)对于BinaryFormatter,对象中的事件字段、ICommand字段都不能序列化。对于ICommand字段,需要加上[Nonserialized]特性;对于事件对象event,不能用[Nonserialized],而应该使用[field:Nonserialized]特性。
(2)BinaryFormatter能够对所有字段(私有、保护、公有)进行序列化和反序列化,而XmlSerializer只能对共有字段进行序列化。
(3)使用BinaryFormatter时,当调用Deserialize时,系统从文件读取数据构件.net对象,然而,在构件.net对象的过程中,不会调用对象的任何一个构造函数。而有时常常在构造函数中配置字段、关注事件、初始化ICommand对象等动作。
首先,对于配置字段,是不存在问题的,因为所有字段都会被原封不动的复制过来,也就是相当于新生成的对象的各字段都是相当于clone过来的,不需要再次在构造函数中去配置了。
其次,对于ICommand对象的初始化,不要放在构造函数中进行,而要直接放在ICommand属性的get方法中初始化,这样就不会出现问题。
最后,对于关注事件,则由于缺乏对构造函数的调用,就无法关注事件了,因此,需要为类定制序列化的特定方法。.net平台提供了多个生命周期的事件处理方法,用特性[OnDeserializing]、[OnDeserialized]、[OnSerializing]、[OnSerialized]来配置特定的方法。
[ISerializable]
public class ProjectInfo
{
public ProjectInfo()
{
//(1)doing something here for field initializing
//(2)说明:不要在构造函数中为ICommand对象进行初始化,ICommand对象的初始化直接放到其属性的get方法中
//(3)以下是对事件关注的代码
//OnSerialized(default(StreamingContext)); //对关注事件的处理.StreamingContext是结构体。另一种方法是直接在此处放置代码
EventInitializer(); //用这个更简洁一些,无需在每次调用构造函数的时候都创建一个default(StreamingContext)
}
[OnDeserializing]
private void OnSerializing(StreamingContext context)
{
}
[OnDeserialized]
private void OnSerialized(StreamingContext context)
{
//这个方法中,可以为关注事件填写相关代码
EventRemedy();
EventInitialzier(); //构造函数中也会调用的代码
}
private void EventRemedy()
{
//这个方法仅可以被OnDeserialized调用。
//由于程序之前在添加集合对象或关联对象的时候,可能关注了事件,但是由于反序列化程序不会去重新执行一次上面的有关程序代码,因此当反序列化后就会漏掉了对这些集合对象条目或关联对象的事件关注。因此,这里需要采取补救措施。也就是对已经存在的这些集合对象条目或关联对象进行事件的关注。比如原来有个ObservableCollection<Child>属性,在AddChild函数中关注了Child的XXXEvent事件,反序列化完成的时候,这个关注的信息就丢失了,因此这里要补充关注一下
}
private void EventInitializer()
{
//用于在构造函数中执行的关注事件的代码
}
[field:NonSerialized]
public event EventHandler MyEvent;
[NonSerialized]
private ICommand _myCommand;
public ICommand MyCommand
{
get
{
if(_myCommand==null)
_myCommand=new DelegateCommand(MyCommandHandler); //命令对象的初始化
return _myCommand;
}
}
private void MyCommandHandler()
{
//concrete logical code for MyCommand
}
}
(4)****非常重要****。需要说明的是,反序列化的过程(从文件-->对象的过程),不是new出来新对象,然后对其进行赋值的。反序列化的时候,不是依据类代码对各个成员进行初步赋值,然后执行构造函数的过程。在反序列化的时候,既不会为成员初赋值,也不会执行构造函数。例如:
[Serializable]
public class MyClass
{
public MyClass()
{
//在反序列化的时候,不会执行构造函数
}
private int x=10; //在反序列化的时候,不是将10赋值给x。而是从数据文件中读取相应的值,直接赋值给x。
[NonSerializable]
private int y=15; //在反序列化的时候,不会将15赋值给y。由于y标注了[NonSerializable],因此,反序列化完成的时候,y的值是default(int),也就是0。
private List<string> Strs=new List<string>(); //在反序列化的时候,不会new一个将List<string>并且赋值给Strs。而是从数据文件中读取相应的值,直接赋值给Strs。
[NonSerializable]
private List<string> AnotherStrs=new List<string>(); //在反序列化的时候,不会new一个将List<string>并且赋值给AnotherStrs。由于AnotherStrs标注了[NonSerializable],因此,反序列化完成的时候,AnotherStrs的值是default(List<string>),也就是null。
}
(四)***非常重要***:OnSerializing和OnDeserilized方法与类的继承
(1) 当类B继承于类A的时候(即B是A的子类),如果A和B中都包含OnSerializing和OnDeserialized方法,这里分两种情况。
(A)父类A和子类B中的OnSerializing和OnDeserialized方法都是私有的,则执行的顺序为:
(*)在序列化的时候,先执行父类的OnSerializing,,然后执行子类的OnSerializing。
(*)在反序列化的时候,先执行父类的OnDeserialized,,然后执行子类的OnDeserialized。
----也就是说,上面这种情况,无论是序列化还是反序列化,都是先执行父类中的方法,然后执行子类中的方法。-
(B)如果父类中的方法是protected或public,则必须加上virtual,然而实际情况是,如果加上virtual,则程序无法运行。也就是OnSerializing和OnDeserialized方法都不存在子类覆写(override)父类方法的情况,否则是无法运行的(visual studio显示:应用程序处于中断模式)。
(2)如果父类中存在OnSerializing和OnDeserialized方法,且方法都是父类私有的,即private。那么在序列化和反序列化的时候,父类中的OnSerializing和OnDeserialized依旧会执行的。当然,如果把private修改为protected或public,结果是一样的。这里可以看出,如果从面向对象的角度看,父类中的private是不能被子类调用的,但实际上还是调用了,实际上这是系统调用的,不是子类调用的。和面向对象思想不同,虽然是父类的私有方法,照样会被调用。
(3)如果子类中存在OnSerializing和OnDeserialized方法,而父类中不存在这些方法,且是私有的即private。那么在序列化和反序列化的时候,子类中的OnSerializing和OnDeserialized依旧会执行的。 当然,如果把private修改为protected或public,结果也是一样的。
(五)一些不能被序列化的字段的处理方法
对于采用类库中的一些类型(如WPF程序常用的SolidColorBrush,TranslateTransform,ScaleTransform,RotateTransform),由于这些类库中的类型没有被开发厂商标注为Serializable的类型,因此无法序列化到文件中。在程序中用到的时候,如果使用它们的类型要实现序列化,就比对给这些类型加上[NonSerialized],程序才能编译通过。但这样一来,在序列化的时候就会丢失数据,怎么办?可行的方法之一就是给程序加上OnSerializing和OnSerialized方法,并增加可以被序列化的字段(记作TransferDataField)用于保存这些无法被序列化类型中的关键数据。也就是说,在OnSerializing方法中,将无法序列化的[NonSerialized]字段中的关键数据保存到TransferDataField,在OnSerialized方法中,利用TransferDataField字段中的数据重新new出来一个对象赋值给无法序列化的[NonSerialized]字段。如下所示,对一些字段进行序列化处理方式。
[OnSerializing]
protected virtual void OnSerializing(StreamingContext context)
{
if (rtData == null || rtData.Length != 3)
rtData = new double[3];
rtData[0] = _rotateTransform.Angle;
rtData[1] = _rotateTransform.CenterX;
rtData[2] = _rotateTransform.CenterY;
if (ttData == null || ttData.Length != 2)
ttData = new double[2];
ttData[0] = _translateTransform.X;
ttData[1] = _translateTransform.Y;
if (stData == null || stData.Length != 4)
stData = new double[4];
stData[0] = _scaleTransform.ScaleX;
stData[1] = _scaleTransform.ScaleY;
stData[2] = _scaleTransform.CenterX;
stData[3] = _scaleTransform.CenterY;
auxiliaryElementLineStrokeData=_auxiliaryElementLineStroke.Color.ToString();
strokeData=_stroke.Color.ToString();
}
[OnDeserialized]
protected virtual void OnDeserialized(StreamingContext context)
{
if (rtData != null && rtData.Length == 3)
_rotateTransform = new RotateTransform(rtData[0], rtData[1], rtData[2]);
if (ttData != null && ttData.Length == 2)
_translateTransform = new TranslateTransform(ttData[0], ttData[1]);
if (stData != null && stData.Length == 4)
_scaleTransform = new ScaleTransform(stData[0], stData[1], stData[2], stData[3]);
_transformGroup = new TransformGroup();
TransformGroup.Children.Add(ScaleTransform);
TransformGroup.Children.Add(TranslateTransform);
BrushConverter brushConverter = new BrushConverter();
_auxiliaryElementLineStroke = (SolidColorBrush)brushConverter.ConvertFromString(auxiliaryElementLineStrokeData);
_stroke = (SolidColorBrush)brushConverter.ConvertFromString(strokeData);
}
// TransferDateField.用于存储RotateTransform、TranslateTranslate和ScaleTransform的数据,便于反序列化.
private double[] rtData,ttData, stData;
// TransferDateField.用于存储_auxiliaryElementLineStroke、_stroke的数据,便于反序列化.
private string auxiliaryElementLineStrokeData, strokeData;
[NonSerialized]
private RotateTransform _rotateTransform=new RotateTransform();
public RotateTransform RotateTransform
{
get { return _rotateTransform; }
set
{
_rotateTransform = value;
}
}
[NonSerialized]
private TranslateTransform _translateTransform = new TranslateTransform();
public TranslateTransform TranslateTransform
{
get { return _translateTransform; }
}
[NonSerialized]
private ScaleTransform _scaleTransform = new ScaleTransform();
public ScaleTransform ScaleTransform
{
get { return _scaleTransform; }
}
[NonSerialized]
private SolidColorBrush _auxiliaryElementLineStroke = new SolidColorBrush(Colors.Gray);
public SolidColorBrush AuxiliaryElementLineStroke
{
get
{
return _auxiliaryElementLineStroke;
}
set
{
_auxiliaryElementLineStroke = value;
OnPropertyChanged("AuxiliaryElementLineStroke");
}
}
[NonSerialized]
private SolidColorBrush _stroke = new SolidColorBrush(Colors.Black);
public virtual SolidColorBrush Stroke
{
get { return _stroke; }
set
{
_stroke = value;
OnPropertyChanged("Stroke");
}
}
(六)选用方式
对于应用程序需要保存为文件的情况,我个人倾向于BinaryFormatter,而不是XmlSerializer,因为后者不能对私有和保护级别的字段进行序列化。对于需要网络传输的Web Api数据,我个人倾向于XmlSerializer。
(补充:事件一定要用加上特性[field:NonSerialized],要不然在序列化的时候,会需要一起序列化订阅者对象,导致无法序列化的异常,曾经采坑并为此找了好久原因。比如在UI对象中订阅ViewModel中的对象的事件,则会需要序列化UI对象,而这是不正确的,会导致错误。)
————————————————
版权声明:本文为CSDN博主「jiuzaizuotian2014」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jiuzaizuotian2014/article/details/80974583
c#序列化和反序列化《转载》的更多相关文章
- C# 序列化与反序列化几种格式的转换
这里介绍了几种方式之间的序列化与反序列化之间的转换 首先介绍的如何序列化,将object对象序列化常见的两种方式即string和xml对象; 第一种将object转换为string对象,这种比较简单没 ...
- 使用Newtonsoft.Json.dll(JSON.NET)动态解析JSON、.net 的json的序列化与反序列化(一)
在开发中,我非常喜欢动态语言和匿名对象带来的方便,JSON.NET具有动态序列化和反序列化任意JSON内容的能力,不必将它映射到具体的强类型对象,它可以处理不确定的类型(集合.字典.动态对象和匿名对象 ...
- Java 序列化与反序列化
1.什么是序列化?为什么要序列化? Java 序列化就是指将对象转换为字节序列的过程,而反序列化则是只将字节序列转换成目标对象的过程. 我们都知道,在进行浏览器访问的时候,我们看到的文本.图片.音频. ...
- C#中怎样实现序列化和反序列化
我们想要将数据进行持久化的操作的话,也就是将数据写入到文件中,我们在C#中可以通过IO流来操作,同时也可以通过序列化来操作,本人是比较推荐使用序列化操作的 因为我们如果想要将一个对象持久化到文件中 如 ...
- Java序列化与反序列化
Java序列化与反序列化是什么?为什么需要序列化与反序列化?如何实现Java序列化与反序列化?本文围绕这些问题进行了探讨. 1.Java序列化与反序列化 Java序列化是指把Java对象转换为字节序列 ...
- XPatchLib 对象增量数据序列化及反序列化器 For .Net
在日常的软件开发和使用过程中,我们发现同一套系统的同一配置项在不同的客户环境中是存在各种各样的差异的.在差异较为分散时,如何较好的管理这些差异,使得维护过程能够更加安全和快速,一直在这样那样的困扰着开 ...
- c# Json 自定义类作为字典键时,序列化和反序列化的处理方法
一般情况下,Newtonsoft.Json.dll 对 Dictionary<int,object>.Dictionary<string,object>等序列化与反序列化都是成 ...
- java 对象序列化与反序列化
Java序列化与反序列化是什么? 为什么需要序列化与反序列化? 如何实现Java序列化与反序列化? 本文围绕这些问题进行了探讨. 1.Java序列化与反序列化 Java序列化是指把Java对象转换为 ...
- 序列化,反序列化和transient关键字
一.序列化和反序列化的概念 序列化:指把java对象转换为字节序列的过程. 反序列化:指把字节序列恢复为java对象的过程. 对象的序列化主要有两种用途: 1) 把对象的字节序列保存到硬盘上,通常存放 ...
- C#对象序列化与反序列化zz
C#对象序列化与反序列化(转载自:http://www.cnblogs.com/LiZhiW/p/3622365.html) 1. 对象序列化的介绍........................ ...
随机推荐
- Revolving Digits(hdu4333)
Revolving Digits Time Limit: 3000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) ...
- 【漏洞复现】ThinkAdmin v5和v6 未授权列目录任意文件读取(CVE-2020-25540)
ThinkAdmin v5和v6 未授权列目录/任意文件读取(CVE-2020-25540) 漏洞简介 ThinkAdmin是一套基于ThinkPHP框架的通用后台管理系统.ThinkAdmin v6 ...
- 「算法笔记」FHQ-Treap
右转→https://www.cnblogs.com/mytqwqq/p/15057231.html 下面放个板子 (禁止莱莱白嫖板子) P3369 [模板]普通平衡树 #include<bit ...
- Java高级程序设计笔记 • 【第2章 多线程(一)】
全部章节 >>>> 本章目录 2.1 线程的概述 2.1.1 进程 2.1.2 多线程优势 2.1.3 Thread 类 2.1.4 实践练习 2.2 Runnable接口 ...
- STC8H开发(一): 在Keil5中配置和使用FwLib_STC8封装库(图文详解)
介绍 FwLib_STC8 是一个针对STC8G, STC8H系列MCU的C语言封装库, 适用于基于这些MCU的快速原型验证. 项目地址: Gitee FwLib_STC8 镜像地址: GitHub ...
- 树形DP总结基础
概念 应用 例题 最大独立子集 没有上司的晚会 题目描述 分析 树的重心 题目描述 分析 树的直径 概念 题目描述 分析 概念 给定一棵有N个节点的树(通常是无根树,也就是有N-1条无向边),我们可以 ...
- Android 摄像头预览悬浮窗,可拖动,可显示在其他app上方
市面上常见的摄像头悬浮窗,如微信.手机QQ的视频通话功能,有如下特点: 整屏页面能切换到一个小的悬浮窗 悬浮窗能运行在其他app上方 悬浮窗能跳回整屏页面,并且悬浮窗消失 我们探讨过用CameraX打 ...
- Jenkins_忘记管理员密码的处理方法
1.查看jenkins配置存放目录 2.修改config.xml的useSecurity的true为flase 3.重启jenkins服务 4.进入jenkins,不输入密码直接就进入了jenkins ...
- c# - 实体类和有参无参构造函数的具体写法
1.前言 与Java基本一模一样,但是rider貌似没有意见生成get和set方法的指令 2.操作 (1)目录 实体源码 namespace ConsoleApp1.entity { public c ...
- 使用 try-catch
ECMA-262 第 3 版引入了 try-catch 语旬,当 try-catch 语句中发生错误时, 浏览器会认为错误已经被处理了 ,因而不会报告错误.对于那些不要求用户懂技术,也不需要用户理解错 ...