博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
配置保存 序列化
阅读量:5151 次
发布时间:2019-06-13

本文共 6955 字,大约阅读时间需要 23 分钟。

简介
是指将对象实例的状态存储到存储媒体的过程。在此过程中,先将对象的公共字段和私有字段以及类的名称(包括类所在的程序集)转换为字节流,然后再把字节流写入数据流。在随后对对象进行反 时,将创建出与原对象完全相同的副本。
在面向对象的环境中实现 机制时,必须在易用性和灵活性之间进行一些权衡。只要您对此过程有足够的控制能力,就可以使该过程在很大程度上自动进行。例如,简单的二进制 不能满足需要,或者,由于特定原因需要确定类中那些字段需要 。以下各部分将探讨 .NET 框架提供的可靠的 机制,并着重介绍使您可以根据需要自定义 过程的一些重要功能。
持久存储
我们经常需要将对象的字段值保存到磁盘中,并在以后检索此数据。尽管不使用 也能完成这项工作,但这种方法通常很繁琐而且容易出错,并且在需要跟踪对象的层次结构时,会变得越来越复杂。可以想象一下编写包含大量对象的大型业务应用程序的情形,程序员不得不为每一个对象编写代码,以便将字段和属性保存至磁盘以及从磁盘还原这些字段和属性。 提供了轻松实现这个目标的快捷方法。
公共语言运行时 (CLR) 管理对象在内存中的分布,.NET 框架则通过使用反射提供自动的 机制。 后,类的名称、程序集以及类实例的所有数据成员均被写入存储媒体中。对象通常用成员变量来存储对其他实例的引用。类 后, 引擎将跟踪所有已 的引用对象,以确保同一对象不被 多次。.NET 框架所提供的 体系结构可以自动正确处理对象图表和循环引用。对对象图表的唯一要求是,由正在进行 的对象所引用的所有对象都必须标记为  (请参阅基本 )。否则,当 程序试图 未标记的对象时将会出现异常。
当反 已 的类时,将重新创建该类,并自动还原所有数据成员的值。
按值封送
对象仅在创建对象的应用程序域中有效。除非对象是从 MarshalByRefObject 派生得到或标记为  ,否则,任何将对象作为参数传递或将其作为结果返回的尝试都将失败。如果对象标记为  ,则该对象将被自动 ,并从一个应用程序域传输至另一个应用程序域,然后进行反 ,从而在第二个应用程序域中产生出该对象的一个精确副本。此过程通常称为按值封送。
如果对象是从 MarshalByRefObject 派生得到,则从一个应用程序域传递至另一个应用程序域的是对象引用,而不是对象本身。也可以将从 MarshalByRefObject 派生得到的对象标记为  。远程使用此对象时,负责进行 并已预先配置为 SurrogateSelector 的格式化程序将控制 过程,并用一个代理替换所有从 MarshalByRefObject 派生得到的对象。如果没有预先配置为 SurrogateSelector, 体系结构将遵从下面的标准 规则(请参阅 过程的步骤)。
基本序列化
要使一个类可 ,最简单的方法是使用   属性对它进行标记,如下所示:
复制  
保存
[Serializable]public class MyObject{public int n1 = 0;public int n2 = 0;public String str = null;}
以下代码片段说明了如何将此类的一个实例 为一个文件:
复制  
保存
MyObject obj = new MyObject();obj.n1 = 1;obj.n2 = 24;obj.str = "一些字符串";IFormatter formatter = new BinaryFormatter();Stream stream = new FileStream("MyFile.bin", FileMode.Create, FileAccess.Write, FileShare.None);formatter.Serialize(stream, obj);stream.Close();
本例使用二进制格式化程序进行 。您只需创建一个要使用的流和格式化程序的实例,然后调用格式化程序的 Serialize 方法。流和要 的对象实例作为参数提供给此调用。类中的所有成员变量(甚至标记为 private 的变量)都将被 ,但这一点在本例中未明确体现出来。在这一点上,二进制 不同于只 公共字段的 XML  程序。
将对象还原到它以前的状态也非常容易。首先,创建格式化程序和流以进行读取,然后让格式化程序对对象进行反 。以下代码片段说明了如何进行此操作。
复制  
保存
IFormatter formatter = new BinaryFormatter();Stream stream = new FileStream("MyFile.bin", FileMode.Open,FileAccess.Read, FileShare.Read);MyObject obj = (MyObject) formatter.Deserialize(fromStream);stream.Close();// 下面是证明Console.WriteLine("n1: {0}", obj.n1);Console.WriteLine("n2: {0}", obj.n2);Console.WriteLine("str: {0}", obj.str);
上面所使用的 BinaryFormatter 效率很高,能生成非常紧凑的字节流。所有使用此格式化程序 的对象也可使用它进行反 ,对于 将在 .NET 平台上进行反 的对象,此格式化程序无疑是一个理想工具。需要注意的是,对对象进行反 时并不调用构造函数。对反 添加这项约束,是出于性能方面的考虑。但是,这违反了对象编写者通常采用的一些运行时约定,因此,开发人员在将对象标记为可 时,应确保考虑了这一特殊约定。
如果要求具有可移植性,请使用 SoapFormatter。所要做的更改只是将以上代码中的格式化程序换成 SoapFormatter,而 Serialize 和 Deserialize 调用不变。对于上面使用的示例,该格式化程序将生成以下结果。
复制  
保存
1
24
一些字符串
<SOAP-ENV:Envelope xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP- ENC=http://schemas.xmlsoap.org/soap/encoding/ xmlns:SOAP- ENV=http://schemas.xmlsoap.org/soap/envelope/ SOAP-ENV:encodingStyle= "http://schemas.microsoft.com/soap/encoding/clr/1.0 http://schemas.xmlsoap.org/soap/encoding/" xmlns:a1="http://schemas.microsoft.com/clr/assem/ToFile"> <SOAP-ENV:Body> <a1:MyObject id="ref-1"> <n1>1</n1> <n2>24</n2> <str id="ref-3">一些字符串</str> </a1:MyObject> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
需要注意的是,无法继承   属性。如果从 MyObject 派生出一个新的类,则这个新的类也必须使用该属性进行标记,否则将无法 。例如,如果试图 以下类实例,将会显示一个 SerializationException,说明 MyStuff 类型未标记为可 。
复制  
保存
public class MyStuff : MyObject{public int n3;}
使用 属性非常方便,但是它存在上述的一些限制。有关何时标记类以进行 (因为类编译后就无法再 ),请参考有关说明(请参阅下面的 规则)。
选择性序列化
类通常包含不应被 的字段。例如,假设某个类用一个成员变量来存储线程 ID。当此类被反 时, 此类时所存储的 ID 对应的线程可能不再运行,所以对这个值进行 没有意义。可以通过使用 NonSerialized 属性标记成员变量来防止它们被 ,如下所示:
复制  
保存
[Serializable]public class MyObject{public int n1;[NonSerialized]public int n2;public String str;}
自定义序列化
可以通过在对象上实现 I  接口来自定义 过程。这一功能在反 后成员变量的值失效时尤其有用,但是需要为变量提供值以重建对象的完整状态。要实现 I ,需要实现 GetObjectData 方法以及一个特殊的构造函数,在反 对象时要用到此构造函数。以下代码示例说明了如何在前一部分中提到的 MyObject 类上实现 I 。
复制  
保存
[Serializable]public class MyObject : ISerializable{public int n1;public int n2;public String str;public MyObject(){}protected MyObject(SerializationInfo info, StreamingContext context){n1 = info.GetInt32("i");n2 = info.GetInt32("j");str = info.GetString("k");}public virtual void GetObjectData(SerializationInfo info, StreamingContext context){info.AddValue("i", n1);info.AddValue("j", n2);info.AddValue("k", str);}}
在 过程中调用 GetObjectData 时,需要填充方法调用中提供的 SerializationInfo 对象。只需按名称/值对的形式添加将要 的变量。其名称可以是任何文本。只要已 的数据足以在反 过程中还原对象,便可以自由选择添加至 SerializationInfo 的成员变量。如果基对象实现了 I ,则派生类应调用其基对象的 GetObjectData 方法。 
需要强调的是,将 I  添加至某个类时,需要同时实现 GetObjectData 以及特殊的构造函数。如果缺少 GetObjectData,编译器将发出警告。但是,由于无法强制实现构造函数,所以,缺少构造函数时不会发出警告。如果在没有构造函数的情况下尝试反 某个类,将会出现异常。在消除潜在安全性和版本控制问题等方面,当前设计优于 SetObjectData 方法。例如,如果将 SetObjectData 方法定义为某个接口的一部分,则此方法必须是公共方法,这使得用户不得不编写代码来防止多次调用 SetObjectData 方法。可以想象,如果某个对象正在执行某些操作,而某个恶意应用程序却调用此对象的 SetObjectData 方法,将会引起一些潜在的麻烦。
在反 过程中,使用出于此目的而提供的构造函数将 SerializationInfo 传递给类。对象反 时,对构造函数的任何可见性约束都将被忽略,因此,可以将类标记为 public、protected、internal 或 private。一个不错的办法是,在类未封装的情况下,将构造函数标记为 protect。如果类已封装,则应标记为 private。要还原对象的状态,只需使用 时采用的名称,从 SerializationInfo 中检索变量的值。如果基类实现了 I ,则应调用基类的构造函数,以使基础对象可以还原其变量。
如果从实现了 I  的类派生出一个新的类,则只要新的类中含有任何需要 的变量,就必须同时实现构造函数以及 GetObjectData 方法。以下代码片段显示了如何使用上文所示的 MyObject 类来完成此操作。
复制  
保存
[Serializable]public class ObjectTwo : MyObject{public int num;public ObjectTwo(): base(){}protected ObjectTwo(SerializationInfo si, StreamingContext context): base(si, context){num = si.GetInt32("num");}public override void GetObjectData(SerializationInfo si, StreamingContext context){base.GetObjectData(si, context);si.AddValue("num", num);}}
切记要在反 构造函数中调用基类,否则,将永远不会调用基类上的构造函数,并且在反 后也无法构建完整的对象。
对象被彻底重新构建,但是在反系列化过程中调用方法可能会带来不良的副作用,因为被调用的方法可能引用了在调用时尚未反 的对象引用。如果正在进行反 的类实现了IDeserializationCallback,则反 整个对象图表后,将自动调用 OnSerialization 方法。此时,引用的所有子对象均已完全还原。有些类不使用上述事件侦听器,很难对它们进行反 ,散列表便是一个典型的例子。在反 过程中检索关键字/值对非常容易,但是,由于无法保证从散列表派生出的类已反 ,所以把这些对象添加回散列表时会出现一些问题。因此,建议目前不要在散列表上调用方法。
序列化过程的步骤
在格式化程序上调用 Serialize 方法时, 按照以下规则进行:
  1. 检查格式化程序是否有代理选取器。如果有,检查代理选取器是否处理指定类型的对象。如果选取器处理此对象类型,将在代理选取器上调用 ISerializable.GetObjectData。 
  2. 如果没有代理选取器或有却不处理此类型,将检查是否使用 Serializable 属性对对象进行标记。如果未标记,将会引发 SerializationException。 
  3. 如果对象已被正确标记,将检查对象是否实现了 ISerializable。如果已实现,将在对象上调用 GetObjectData。 
  4. 如果对象未实现 Serializable,将使用默认的序列化策略,对所有未标记为 NonSerialized 的字段都进行序列化。
版本控制
.NET 框架支持版本控制和并排执行,并且,如果类的接口保持一致,所有类均可跨版本工作。由于 涉及的是成员变量而非接口,所以,在向要跨版本 的类中添加成员变量,或从中删除变量时,应谨慎行事。特别是对于未实现 I  的类更应如此。若当前版本的状态发生了任何变化(例如添加成员变量、更改变量类型或更改变量名称),都意味着如果同一类型的现有对象是使用早期版本进行 的,则无法成功对它们进行反 。
如果对象的状态需要在不同版本间发生改变,类的作者可以有两种选择:
  • 实现 ISerializable。这使您可以精确地控制序列化和反序列化过程,在反序列化过程中正确地添加和解释未来状态。 
  • 使用 NonSerialized 属性标记不重要的成员变量。仅当预计类在不同版本间的变化较小时,才可使用这个选项。例如,把一个新变量添加至类的较高版本后,可以将该变量标记为 NonSerialized,以确保该类与早期版本保持兼容。
序列化规则
由于类编译后便无法 ,所以在设计新类时应考虑 。需要考虑的问题有:是否必须跨应用程序域来发送此类?是否要远程使用此类?用户将如何使用此类?也许他们会从我的类中派生出一个需要 的新类。只要有这种可能性,就应将类标记为可 。除下列情况以外,最好将所有类都标记为可 :
所有的类都永远也不会跨越应用程序域。如果某个类不要求 但需要跨越应用程序域,请从 MarshalByRefObject 派生此类。 
类存储仅适用于其当前实例的特殊指针。例如,如果某个类包含非受控的内存或文件句柄,请确保将这些字段标记为 NonSerialized 或根本不 此类。 
某些数据成员包含敏感信息。在这种情况下,建议实现 I  并仅 所要求的字段。

转载于:https://www.cnblogs.com/jciwolf/articles/1031464.html

你可能感兴趣的文章
C++有关 const & 内敛 & 友元&静态成员那些事
查看>>
函数积累
查看>>
Swift 入门之简单语法(六)
查看>>
shim和polyfill有什么区别
查看>>
〖Python〗-- IO多路复用
查看>>
栈(括号匹配)
查看>>
Java学习 · 初识 面向对象深入一
查看>>
源代码如何管理
查看>>
vue怎么将一个组件引入另一个组件?
查看>>
bzoj1040: [ZJOI2008]骑士
查看>>
LeetCode 74. Search a 2D Matrix(搜索二维矩阵)
查看>>
利用SignalR来同步更新Winfrom
查看>>
反射机制
查看>>
CocoaPod
查看>>
BZOJ 1251: 序列终结者 [splay]
查看>>
5G边缘网络虚拟化的利器:vCPE和SD-WAN
查看>>
MATLAB基础入门笔记
查看>>
【UVA】434-Matty&#39;s Blocks
查看>>
Android开发技术周报 Issue#80
查看>>
hadoop2.2.0+hive-0.10.0完全分布式安装方法
查看>>