NET中对象序列化研究论文

时间:2022-09-17 05:34:00

导语:NET中对象序列化研究论文一文来源于网友上传,不代表本站观点,若需要原创文章可咨询客服老师,欢迎参考。

NET中对象序列化研究论文

摘要实现序列化最重要的两个原因是:将对象的状态保存在存储媒体中以便以后重新创建出完全相同的副本;按值将对象从一个应用程序域发送至另一个应用程序域。例如,序列化可用于在中保存会话状态;将对象复制到Windows窗体的剪贴板中;它还可用于按值将对象从一个应用程序域远程传递至另一个应用程序域。本文简要介绍了中使用的序列化。

关键词.net;序列化;封送

1引言

序列化是指将对象实例的状态存储到存储媒体的过程。在此过程中,先将对象的公共字段和私有字段以及类的名称(包括类所在的程序集)转换为字节流,然后再把字节流写入数据流。在随后对对象进行反序列化时,将创建出与原对象完全相同的副本。

在面向对象的环境中实现序列化机制时,必须在易用性和灵活性之间进行一些权衡。只要对此过程有足够的控制能力,就可以使该过程在很大程度上自动进行。例如,简单的二进制序列化不能满足需要,或者,由于特定原因需要确定类中哪些字段需要序列化。以下各部分将探讨.NET框架提供的可靠的序列化机制,并着重介绍如何根据需要自定义序列化过程。

2持久存储

我们经常需要将对象的字段值保存到磁盘中,并在以后在内存中还原次对象。尽管不使用序列化也能完成这项工作,但这种方法通常很繁琐而且容易出错,并且在需要跟踪对象的层次结构时,会变得越来越复杂。可以想象一下编写包含大量对象的大型业务应用程序的情形,程序员不得不为每一个对象编写代码,以便将字段和属性保存至磁盘以及从磁盘还原这些字段和属性。序列化提供了轻松实现这个目标的快捷方法。

公共语言运行时(CLR)管理对象在内存中的分布,.NET框架则通过使用反射提供自动的序列化机制。对象序列化后,类的名称、程序集以及类实例的所有数据成员均被写入存储媒体中。对象通常用成员变量来存储对其它实例的引用。类序列化后,序列化引擎将跟踪所有已序列化的引用对象,以确保同一对象不被序列化多次。.NET框架所提供的序列化体系结构可以自动正确处理对象图表和循环引用。对对象图表的惟一要求是,由正在进行序列化的对象所引用的所有对象都必须标记为Serializable。否则,当序列化程序试图序列化未标记的对象时将会出现异常。当反序列化已序列化的类时,将重新创建该类的对象,并自动还原所有数据成员的值。

3按值封送

按值封送是指将对象序列化为字节流,并从一个应用程序域传输至另一个应用程序域,然后进行反序列化,从而在第二个应用程序域中产生出该对象的一个副本,这在COM技术中经常提到。在.Net中,对象仅在创建对象的应用程序域中有效,除非对象是从MarshalByRefObject派生得到或标记为Serializable,否则,任何将对象作为参数传递或作为结果返回到另外一个应用程序域都将失败。

如果对象标记为Serializable,则该对象将被自动序列化,并从一个应用程序域传输至另一个应用程序域,然后进行反序列化,从而在第二个应用程序域中产生出该对象的一个精确副本。

如果对象是从MarshalByRefObject派生得到,则从一个应用程序域传递至另一个应用程序域的是对象引用,而不是对象本身。也可以将从MarshalByRefObject派生得到的对象标记为Serializable。远程使用此对象时,负责进行序列化并已预先配置为SurrogateSelector的格式化程序将控制序列化过程,并用一个替换所有从MarshalByRefObject派生得到的对象。如果没有预先配置为SurrogateSelector,序列化体系结构将遵从下面的标准序列化规则。

4基本序列化

要使一个类可序列化,最简单的方法是使用Serializable属性对它进行标记,如下所示:

[Serializable]

publicclassMyObject

{

publicintn1=0;

publicintn2=0;

publicStringstr=null;

}

以下代码片段说明了如何将此类的一个实例序列化为一个文件:

MyObjectobj=newMyObject();

obj.n1=1;

obj.n2=24;

obj.str="一些字符串";

IFormatterformatter=newBinaryFormatter();

Streamstream=newFileStream("MyFile.bin",FileMode.Create,FileAccess.Write,FileShare.None);

formatter.Serialize(stream,obj);

stream.Close();

本例使用二进制格式化程序进行序列化。只需创建一个要使用的流和格式化程序的实例,然后调用格式化程序的Serialize方法。流和要序列化的对象实例作为参数提供给此调用。类中包括private变量的所有成员变量,都将被序列化,但这一点在本例中未明确体现出来。在这一点上,二进制序列化不同于只序列化公共字段的XML序列化程序。

将对象还原到它以前的状态也非常容易。首先,创建格式化程序和流以进行读取,然后让格式化程序对对象进行反序列化。以下代码片段说明了如何进行此操作。

IFormatterformatter=newBinaryFormatter();

Streamstream=newFileStream("MyFile.bin",FileMode.Open,FileAccess.Read,FileShare.Read);

MyObjectobj=(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调用不变。对于上面使用的示例,该格式化程序将生成以下结果。

<SOAP-ENV:Envelope

xmlns:xsi=http:///2001/XMLSchema-instancexmlns:xsd="http:///2001/XMLSchema"

xmlns:SOAP-ENC=http:///soap/encoding/

xmlns:SOAP-ENV=http:///soap/envelope/

SOAP-ENV:encodingStyle="http:///soap/encoding/clr/1.0http:///soap/encoding/"xmlns:a1="http:///clr/assem/ToFile">

<SOAP-ENV:Body>

<a1:MyObjectid="ref-1">

<n1>1</n1>

<n2>24</n2>

<strid="ref-3">一些字符串</str>

</a1:MyObject>

</SOAP-ENV:Body>

</SOAP-ENV:Envelope>

需要注意的是,无法继承Serializable属性。如果从MyObject派生出一个新的类,则这个新的类也必须使用该属性进行标记,否则将无法序列化。例如,如果试图序列化以下类实例,将会显示一个SerializationException,说明MyStuff类型未标记为可序列化。

publicclassMyStuff:MyObject

{

publicintn3;5选择性序列化

类通常包含不应被序列化的字段。例如,假设某个类用一个成员变量来存储线程ID。当此类被反序列化时,序列化此类时所存储的ID对应的线程可能不再运行,所以对这个值进行序列化没有意义。可以通过使用NonSerialized属性标记成员变量来防止它们被序列化,如下所示:

[Serializable]

publicclassMyObject

{

publicintn1;

[NonSerialized]publicintn2;

publicStringstr;

}

6自定义序列化

可以通过在对象上实现ISerializable接口来自定义序列化过程。这一功能在反序列化后成员变量的值失效时尤其有用,但是需要为变量提供值以重建对象的完整状态。要实现ISerializable,需要实现GetObjectData方法以及一个特殊的构造函数,在反序列化对象时要用到此构造函数。以下代码示例说明了如何在前一部分中提到的MyObject类上实现ISerializable接口。

[Serializable]

publicclassMyObject:ISerializable

{

publicintn1;

publicintn2;

publicStringstr;

publicMyObject(){}

protectedMyObject(SerializationInfoinfo,StreamingContextcontext)

{

n1=info.GetInt32("i");

n2=info.GetInt32("j");

str=info.GetString("k");

}

publicvirtualvoidGetObjectData(SerializationInfoinfo,StreamingContextcontext)

{

info.AddValue("i",n1);

info.AddValue("j",n2);

info.AddValue("k",str);

}

}

在序列化过程中调用GetObjectData时,需要填充方法调用中提供的SerializationInfo对象。只需按名称/值对的形式添加将要序列化的变量。其名称可以是任何文本。只要已序列化的数据足以在反序列化过程中还原对象,便可以自由选择添加至SerializationInfo的成员变量。如果基对象实现了ISerializable,则派生类应调用其基对象的GetObjectData方法。

需要强调的是,将ISerializable添加至某个类时,需要同时实现GetObjectData以及特殊的构造函数。如果缺少GetObjectData,编译器将发出警告。但是,由于无法强制实现构造函数,所以,缺少构造函数时不会发出警告。如果在没有构造函数的情况下尝试反序列化某个类,将会出现异常。

在反序列化过程中,使用出于此目的而提供的构造函数将SerializationInfo传递给类。对象反序列化时,对构造函数的任何可见性约束都将被忽略,因此,可以将类标记为public、protected、internal或private。通常,在类未封装的情况下,将构造函数标记为protect。如果类已封装,则应标记为private。要还原对象的状态,只需使用序列化时采用的名称,从SerializationInfo中检索变量的值。如果基类实现了ISerializable,则应调用基类的构造函数,以使基础对象还原其变量。

如果从实现了ISerializable的类派生出一个新的类,则只要新的类中含有任何需要序列化的变量,就必须同时实现构造函数以及GetObjectData方法。以下代码片段显示了如何使用上文所示的MyObject类来完成此操作。

[Serializable]publicclassObjectTwo:MyObject

{

publicintnum;

publicObjectTwo():base()

{}

protectedObjectTwo(SerializationInfosi,StreamingContextcontext):base(si,context)

{

num=si.GetInt32("num");

}

publicoverridevoidGetObjectData(SerializationInfosi,StreamingContextcontext)

{

base.GetObjectData(si,context);

si.AddValue("num",num);

}

}

切记要在反序列化构造函数中调用基类,否则,将永远不会调用基类上的构造函数,并且在反序列化后也无法构建完整的对象。

7序列化过程的规则

在格式化程序上调用Serialize方法时,对象序列化按照以下规则进行:

检查格式化程序是否有选取器。如果有,检查选取器是否处理指定类型的对象。如果选取器处理此对象类型,将在选取器上调用ISerializable.GetObjectData。

如果没有选取器或有却不处理此类型,将检查是否使用Serializable属性对对象进行标记。如果未标记,将会引发SerializationException。

如果对象已被正确标记,将检查对象是否实现了ISerializable。如果已实现,将在对象上调用GetObjectData。

如果对象未实现Serializable,将使用默认的序列化策略,对所有未标记为NonSerialized的字段都进行序列化。

8版本控制

.NET框架支持版本控制和并排执行,并且,如果类的接口保持一致,所有类均可跨版本工作。由于序列化涉及的是成员变量而非接口,所以,在向要跨版本序列化的类中添加成员变量,或从中删除变量时,应谨慎行事。特别是对于未实现ISerializable的类更应如此。若当前版本的状态发生了任何变化(例如添加成员变量、更改变量类型或更改变量名称),都意味着如果同一类型的现有对象是使用早期版本进行序列化的,则无法成功对它们进行反序列化。

如果对象的状态需要在不同版本间发生改变,类的作者可以有两种选择:

实现ISerializable。这使您可以精确地控制序列化和反序列化过程,在反序列化过程中正确地添加和解释未来状态。

使用NonSerialized属性标记不重要的成员变量。仅当预计类在不同版本间的变化较小时,才可使用这个选项。例如,把一个新变量添加至类的较高版本后,可以将该变量标记为NonSerialized,以确保该类与早期版本保持兼容。

9序列化规则

在设计新类时应考虑序列化。需要考虑的问题有:是否必须跨应用程序域来发送该类的对象?是否要远程使用此类?用户将如何使用此类?是否要派生出一个需要序列化的新类。只要有这种可能性,就应将类标记为可序列化。除下列情况以外,最好将所有类都标记为可序列化:

所有的类都永远也不会跨越应用程序域。如果某个类不要求序列化但需要跨越应用程序域,请从MarshalByRefObject派生此类。

类存储仅适用于其当前实例的特殊指针。例如,如果某个类包含非受控的内存或文件句柄,请确保将这些字段标记为NonSerialized或根本不序列化此类。

某些数据成员包含敏感信息。在这种情况下,建议实现ISerializable并仅序列化所要求的字段。

参考文献

[1]潘爱民著.Com原理与应用.清华大学出版社

[2]SimonRobinsonK.Scott等著.C#高级编程.清华大学出版社

[3]DavidJ.KruglinskiScotWingoGeorgeShepherd著.VC++6.0技术内幕.北京希望电子出版社