详解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 = 0;
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[0]==a1[1]);//true
using(var stream=new MemoryStream()){
BinaryFormatter formatter = new BinaryFormatter();
//先序列化再反序列化
formatter.Serialize(stream,a1);
stream.Position = 0;
Singleton[] a2 = (Singleton[])formatter.Deserialize(stream);
Console.WriteLine(a2[0]==a2[1]);//true
Console.WriteLine(a1[0] == a1[1]);//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 = 0;
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 = 0;
Console.WriteLine(new StreamReader(stream).ReadToEnd());
//反序列化Universal字符串
stream.Position = 0;
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#序列化

浙公网安备 33010602011771号