赞
踩
(一)使用总体说明
.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对象,而这是不正确的,会导致错误。)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。