序列化概念
为什么您想要使用序列化?有两个最重要的原因:一个原因是将对象的状态永久保存在存储媒体中,以便可以在以后重新创建精确的副本;另一个原因是通过值将对象从一个应用程序域发送到另一个应用程序域中。例如,序列化可用于在 ASP.NET 中保存会话状态并将对象复制到 Windows 窗体的剪贴板中。远程处理还可以使用序列化通过值将对象从一个应用程序域传递到另一个应用程序域中。
永久存储
通常需要将一个对象的各字段的值存储到磁盘中,这样以后可以检索这些数据。尽管不依赖序列化也可以很容易地做到这一点,但这样的方法通常十分麻烦并且容易出错,在您需要跟踪对象的层次结构时将变得越来越复杂。设想一下编写包含数以千计对象的大型商业应用程序,将不得不为每一对象编写代码以将字段和属性保存到磁盘上和从磁盘上还原它们,这是多么的复杂。而序列化为实现上述目标提供了一个方便的机制。
公共语言运行库管理对象在内存中的存储方式并通过使用反射提供自动的序列化机制。当序列化一个对象时,类的名称、程序集和类实例的所有数据成员都被写入存储中。对象通常在成员变量中存储对其他实例的引用。在序列化类时,序列化引擎跟踪已被序列化的引用对象,以确保同一对象不会被多次序列化。随 一起提供的序列化结构自动正确处理对象图和循环引用。对于对象图的唯一要求就是,由被序列化的对象引用的所有对象还必须标记为 Serializable(有关更多信息,请参见基本序列化)。如果没有进行此标记,当序列化程序尝试序列化未标记的对象时,将引发一个异常。
当反序列化已序列化的类时,重新创建该类并且自动还原所有数据成员的值。
值封送
对象只在创建它们的应用程序域中有效。将对象作为一个参数传递或将其作为结果返回的任何尝试都将失败,除非该对象派生自 MarshalByRefObject 或被标记为 Serializable。如果该对象被标记为 Serializable,该对象将被自动序列化,从一个应用程序域传输到其他的应用程序域,然后被反序列化以在第二个应用程序域中生成该对象的精确副本。此过程通常被称作值封送。
当对象从 MarshalByRefObject 派生时,从一个应用程序域将对象引用传递到另一个应用程序域,而不是传递该对象本身。还可将从 MarshalByRefObject 派生的对象标记为 Serializable。当该对象与远程处理一起使用时,负责序列化的格式化程序(该格式化程序已由代理选择器 SurrogateSelector 预先配置)控制序列化过程,并用代理代替从 MarshalByRefObject 派生的所有对象。如果没有适当的 SurrogateSelector,则序列化结构遵循在序列化过程的步骤中描述的标准序列化规则。
有选择的序列化
类通常包含不应被序列化的字段。例如,假设一个类将线程 ID 存储在成员变量中。当反序列化该类时,在序列化该类时为其存储 ID 的线程可能不再运行,因此序列化该值没有意义。通过用 NonSerialized 属性标记成员变量,可以防止它们被序列化,如下所示。
public class MyObject
{
public int n1;
[NonSerialized] public int n2;
public String str;
}
[NonSerialized] 只对字段有效
序列化过程的步骤
当对格式化程序调用 Serialize 方法时,对象序列化将根据以下规则序列继续进行:
进行检查以确定格式化程序是否有代理选择器。如果格式化程序有代理选择器,则检查该代理选择器是否处理给定类型的对象。如果选择器处理该对象类型,则对该代理选择器调用 ISerializable.GetObjectData。
如果没有代理选择器或者代理选择器不处理该对象类型,则进行检查以确定是否用 Serializable 属性标记了该对象。如果没有用此属性标记该对象,则引发 SerializationException。
如果用此属性正确标记了该对象,则检查该对象是否实现 ISerializable 接口。如果该对象实现此接口,则对该对象调用 GetObjectData。
如果该对象没有实现 ISerializable,则使用默认的序列化策略,序列化所有未标记为 NonSerialized 的字段。
{
if ( 检查该代理选择器是否处理给定类型的对象 )
{
对该代理选择器调用 ISerializable.GetObjectData
}
}
else
{
if ( 检查以确定是否用 Serializable 属性标记了该对象 )
{
if ( 检查该对象是否实现 ISerializable 接口 )
{
对该对象调用 GetObjectData
}
else
{
使用默认的序列化策略,序列化所有未标记为 NonSerialized 的字段
}
}
else
{
throw new System.SerializationException
}
}
序列化指南
在设计新类时应该考虑序列化,因为编译了类之后,就不能再使该类可序列化了。应该提出下列问题:该类是否需要跨应用程序域发送?该类是否将与远程处理一起使用?用户将使用该类做什么?他们可能会从我的类派生需要序列化的新类吗?如果不确定,请将该类标记为可序列化。最好是将所有类标记为可序列化,除非下列任一条件为真:
类永远不跨应用程序域发送。如果序列化不是必需的并且该类需要跨应用程序域,则从 MarshalByRefObject 派生该类。
类存储只适用于该类的当前实例的特殊指针。例如,如果一个类包含非托管的内存或文件句柄,则确保这些文件使用 NonSerializedAttribute 属性标记,或者根本不序列化该类。
类数据成员包含敏感的信息。这种情况下,最好将类标记为可序列化,但使用 NonSerializedAttribute 属性标记包含敏感信息的个别数据成员。另一种方法是实现 ISerializable 接口并且只序列化必需的字段。
自定义序列化
OnDeserializedAttribute
当应用于某方法时,指定在对象反序列化后立即调用此方法。
OnDeserializingAttribute
当应用至方法时,指定反序列化对象时调用的方法。
OnSerializedAttribute
如果将对象图应用于某方法,则应指定在序列化该对象图后是否调用该方法。
OnSerializingAttribute
当应用于某个方法时,指定在对象序列化前调用此方法。
// 首先运行(序列化)
[OnSerializing()]
internal void OnSerializingMethod(StreamingContext context)
{
member2 = " This value went into the data file during serialization. " ;
}
// 如果将对象图应用于某方法,则应指定在序列化该对象图后是否调用该方法。
// OnSerializing执行完后在运行(序列化)
[OnSerialized()]
internal void OnSerializedMethod(StreamingContext context)
{
member2 = " This value was reset after serialization. " ;
}
// 当应用至方法时,指定反序列化对象时调用的方法。
// 首先运行(反序列化)
[OnDeserializing()]
internal void OnDeserializingMethod(StreamingContext context)
{
member3 = " This value was set during deserialization " ;
}
// 当应用于某方法时,指定在对象反序列化后立即调用此方法。
// OnDeserializing执行完后在运行(反序列化)
[OnDeserialized()]
internal void OnDeserializedMethod(StreamingContext context)
{
member4 = " This value was set after deserialization. " ;
}
MarshalByRefObject 类
允许在支持远程处理的应用程序中跨应用程序域边界访问对象。
using System.Runtime.Remoting;
using System.Security.Permissions;
public class SetObjectUriForMarshalTest {
class TestClass : MarshalByRefObject {
}
[SecurityPermission(SecurityAction.LinkDemand)]
public static void Main() {
TestClass obj = new TestClass();
RemotingServices.SetObjectUriForMarshal(obj, " testUri " );
RemotingServices.Marshal(obj);
Console.WriteLine(RemotingServices.GetObjectUri(obj));
}
}
BinaryFormatter 类
以二进制格式将对象或整个连接对象图形序列化和反序列化。
SoapFormatter 类(不支持泛型的序列化)
以 SOAP 格式将对象或整个连接对象的图形序列化和反序列化。
{
switch (type)
{
case 1 :
return new BinaryFormatter();
case 2 :
return new SoapFormatter();
default :
return new BinaryFormatter();
}
}
// 序列化
public static void Serialize(BuyCarPlanPO po)
{
IFormatter formatter = create( 1 );
Stream stream = new FileStream( " MyFile.bin " , FileMode.Create, FileAccess.Write, FileShare.None);
formatter.Serialize(stream, po);
stream.Close();
}
// 反序列化
public static void Deserialize( ref BuyCarPlanPO po)
{
IFormatter formatter = create( 1 );
Stream stream = new FileStream( " MyFile.bin " , FileMode.Open, FileAccess.Read, FileShare.Read);
po = (BuyCarPlanPO)formatter.Deserialize(stream);
stream.Close();
}