2.6.1 使用Serializable特性
前面已经提到,如果允许运行库串行化一个对象,必须将该类标记上Serializable特性。下面的代码定义了两个标记了Serialable特性的类。
[Serializable]
public class Car
{
private string mColor;
private int mTopSpeed;
//Reference to another object
private Radio mRadio;
[NonSerialized] //Runtime will not serialize this field
private string mNickName;
public Car(string nickName, int topSpeed, string color)
{
mNickName = nickName;
mTopSpeed = topSpeed;
mColor = color;
mRadio = new Radio();
}
}
[Serializable]
public class Radio
{
private int mVolume = 5;
}
如上所示,Car类的mNickName字段用NonSerialized特性标注,指示运行库进行串行化时跳过该项,而其他部分允许进行串行化。这里注意,mRadio是对另一对象的引用,意味着运行库必须对其串行化。事实上,当运行库被告知要对某个对象串行化时,使用给定的对象作为根,建立一个对象图表(类似于垃圾回收器)。所有在图表中的对象也必须可被串行化的,否则运行库将引发异常。
下面的代码举例说明如何将Car对象串行化到一个文件之中。
static void Main(string[] args)
{
Car myCar = new Car("Christine", 150, "Red");
FileStream mySoapFile = File.Create("Car.txt");
// Use a SOAP formatter object to serialize the object.
new SoapFormatter().Serialize(mySoapFile, myCar);
mySoapFile.Close();
}
运行库不控制串行化数据的实际格式,但是有一个专门进行格式化的对象负责对串行化输出进行格式化。.NET框架有两种格式化程序:SOAP格式化程序和二进制格式化程序。在前面的例子中,SoapFormatter对象将对象串行化进一个SOAP报文,并将其发送到特定的流中。图2-26显示了在执行完这段代码后Car.txt中的内容。
如果仔细查看图2-26,很容易发现SOAP是如何描绘每个对象和它的数据成员。这种方式通过增加文件大小来提高了可读性。二进制格式化程序使用了更为紧凑的格式。如果需要在网络间发送对象,这种方式就更重要,尤其是在需要考虑网络带宽而不强调可读性的情况下。
通过调用SoapFormatter.Dewerialize方法,就可将该对象读入内存中:
static void Rehydrate()
{
FileStream mySoapFile = File.Open("Car.txt", FileMode.Open);
Car myCar = (Car) new SoapFormatter().Deserialize(mySoapFile);
mySoapFile.Close();
}
2.6.2 ISerializable接口和Formatter类
在某些情况下,需要比默认的串行化机制更强的功能时,可以实现ISerializable接口。它提供了一种对串行化数据更为精确控制,但不提供对数据格式的控制。这时,就需要扩展Formatter类。
下面的代码演示了如何实现ISerializable接口。
[Serializable]
public class Car : ISerializable
{
private string mColor;
static private int mTopSpeed;
private Radio mRadio;
[NonSerialized]
private string mNickName;
// Required by the ISerializable interface
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("mColor", mColor);
info.AddValue("mTopSpeed", mTopSpeed);
info.AddValue("mRadio", mRadio);
}
// This contructor is required todeserialize the object
private Car(SerializationInfo info, StreamingContext context)
{
mTopSpeed = info.GetInt32("mTopSpeed");
mColor = info.GetString("mColor");
mRadio = (Radio) info.GetValue("mRadio", typeof(Radio));
}
. . .
}
当运行库串行化对象时,将调用ISerializable.GetObjectData方法,并传递SerializationInfo对象和StreamingContext结构。SerializationInfo对象提供AddValue方法,允许在对象中使用相关的名称存储一个值。VB6.0编程人员也许认为SerializationInfo对象是PropertyBag的另一个表示方法,但是与PropertyBag仅存储变量不同,SerializationInfo对象能存储强类型数据。AddValue经过几次重载,允许加入任何一个原始CLR类型。可以研究StreamingContext结构的内容,从而准确地判定加速串行化处理过程的因素是什么。例如,如果一个对象被传递给另一处理过程时,希望采用不同的串行化该对象的方法。如下代码所示:
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
// If serializing across proxess , then skip the radio object
if (context.State != StreamingContextStates.CrossProcess)
{
mRadio = (Radio) info.GetValue("mRadio", typeof(Radio));
}
info.AddValue("mColor", mColor);
info.AddValue("mTopSpeed", mTopSpeed);
}
该例考察StreamingContext结构的State字段。这个枚举指定了被串行化对象的源或目标地。如果它与StreamingContextStates.CrossProcess相同,目的地就是同一机器上的另一进程。在那种情况下,代码不能对包含的Radio对象串行化。
通过对ISerializable接口的实现允许控制“什么”被串行化。而控制对象“如何”被串行化,就必须采用格式化程序类。这里还需要实现其他几种方法,包括Serialize、Deserialize、WriteBoolean、WriteByte、WriteArray等。这比本书实际用到的要深,因此把它留给细心的读者作为练习。
2.7 小结
本章目的是探讨对于分布式程序设计十分重要的.NET基本概念,具体包括:
● 通过构建CTS和CLS,.NET实现了语言和平台的互操作性。
● CLR是对CTS的实现的描述。它是一种为.NET程序提供的围绕Windows操作系统和运行环境的面向对象包装。
● .NET程序集表示一个可控制版本的部署单元。它提供了包含所有定义类型的容器。强名称程序集可用于单个应用程序(私有程序集),或将其安装在GAC中,供其他应用程序使用(共享程序集)。
● 特性为代码项提供附加的信息。有些特性能被编译器和运行库识别,并能指导它们对代码项完成特殊的操作。自定义特性可以自己生成,如果被检测到并通过反射实现操作时是很有用的。
● .NET使用垃圾回收实现资源管理,代替了在COM中使用的引用计数。垃圾回收虽然能很好解决循环引用问题并在多线程环境中提高运行性能,但是不像引用计数,不能提供确定性终止。
● 通过反射,.NET提供了自动类型串行化。如果希望由运行库实现串行化,只需要在希望串行化的对象前使用Serializable特性;并且可通过实现ISerializable接口来控制串行化过程。
当然,.NET的内容很多,不可能在一章中全部涉及。希望通过本章的介绍,为读者在本书后续部分的学习过程中提供必要的知识储备。