1、实例解析 C+/CLI 的串行化-串行化可使对象被转换为某种外部的形式,比如以文件存储的形式供程序使用,或通过程序间的通讯发送到另一个处理过程。转换为外部形式的过程称为“串行化“ ,而逆过程称为“反串行化 “。 简介请看例 1 中的示例,其将多个对象类型的值写入到一个新的磁盘文件中,关闭文件,接着再把这些值重新读取到内存中。例 1:using namespace System;using namespace System:IO;using namespace System:Runtime:Serialization:Formatters:Binary;int main()array intAr
2、ray = 10, 20, 30;array floatArray = 1.2F, 2.4F,3.5F, 6.8F,8.4F, 9.7F;DateTime dt = DateTime:Now;Console:WriteLine(“dt 0Serialize(file, “Hello“);/*3b*/ formatter-Serialize(file, intArray);/*3c*/ formatter-Serialize(file, floatArray);/*3d*/ formatter-Serialize(file, true);/*3e*/ formatter-Serialize(fi
3、le, dt);/*3f*/ formatter-Serialize(file, 1000);/*3g*/ formatter-Serialize(file, LX);/*3h*/ formatter-Serialize(file, 1.23456F);/*4*/ file-Close();/从文件中反串行化数据-即读取数据/*5*/ file = File:Open(“Sr01.ser“, FileMode:Open);/*6a*/ String s = static_cast(formatter-Deserialize(file);Console:WriteLine(“String 0 n
4、ewIntArray = static_cast(formatter-Deserialize(file);Console:WriteLine(“newIntArray:“);for (int i = 0; i Length; +i)Console:Write(“ 0“, newIntArrayi);Console:WriteLine();/*6c*/ array newFloatArray =static_cast(formatter-Deserialize(file);Console:WriteLine(“newFloatArray:“);for (int i = 0; i (formatt
5、er-Deserialize(file);Console:WriteLine(“bool 0(formatter-Deserialize(file);Console:WriteLine(“newDT 0(formatter-Deserialize(file);Console:WriteLine(“int 0(formatter-Deserialize(file);Console:WriteLine(“wchar_t 0(formatter-Deserialize(file);Console:WriteLine(“float 0Close(); 在标记 1 中,我们定义了一个 BinaryFor
6、matter 类型的变量,此种类型的任意对象都可以二进制的形式进行串行与反串行化。在标记 2 中,用指定的名称创建了一个新的文件,后缀 .ser 没有特别的意思,这是约定俗成的表示这是一个串行化数据文件。从标记 3a 至 3h,表示一个对象被串行化至文件中。在字符串的情况下,每个字符都被写入;在数组的情况下,所有元素都被写入;在日期时间的情况下,类型中包含的所有数据及有关依赖项都被写入;在为原始类型值的情况下,它们先被装箱,然后对应的对象被写入。上述动作中,串行化只需要接收一个 Object类型参数的对象即可。通过调用 Deserialize 函数,可取回串行化后的数据,如标记 6a 中所示;
7、因为此函数返回一个 Object类型的值,所以需要把它转换为相应的值。程序的输出如插 1 所示:插 1:例 1 中串行化、反串行化的输出String HelloTrue9/29/2005 3:25:44 PM1000X1.23456ReadLine() != nullptr)/*2*/ dictionary-Add(str, nullptr);inStream-Close();/*3*/ Console:WriteLine(“Dictionary contains 0 entries“, dictionary-Count);BinaryFormatter formatter = gcnew B
8、inaryFormatter();Stream file = File:Open(“dictionary.ser“, FileMode:Create);/*4*/ formatter-Serialize(file, dictionary);file-Close(); 在标记 1 中,我们先分配了一个初始化为 21000 个条目的哈希表(这样做只是为了加快处理速度,在条目相加时不需要重新进行分配) ,接着从一个文本文件中,一次一行地读入字,并将其添加到标记 2 的哈希表中。请注意,在定义中,哈希表的每个条目都由(键/值)对组成。但在我们的程序中,键也是值,所以在第二个参数中使用了 nullprt
9、。哈希表中的键值必须是唯一的,而添加进来的任何类型的对象都必须重载 System:对象名 GetHashCode 函数-字符串也一样。一旦文件中所有的字被读取并添加到哈希表中,就可通过一个简单的 Serialize 调用,把哈希表写到磁盘上,如标记 4 所示。在例 3 中,我们读入这个字典,并在其中查找用户提供的字,插 2 是对应的输出。例 3:using namespace System;using namespace System:IO;using namespace System:Collections;using namespace System:Runtime:Serializati
10、on:Formatters:Binary;int main()BinaryFormatter formatter = gcnew BinaryFormatter;Stream file = File:Open(“dictionary.ser“, FileMode:Open);/*1*/ Hashtable dictionary = static_cast(formatter-Deserialize(file);file-Close();/*2*/ Console:WriteLine(“Dictionary contains 0 entries“, dictionary-Count);Strin
11、g word;while (true)Console:Write(“Enter a word: “);word = Console:ReadLine();if (word = nullptr)break; /*3*/ Console:WriteLine(“01 found“, word, (dictionary-Contains(word) ? “ : “ not“); 插 2:使用反串行化进行字典查找Dictionary contains 20159 entriesEnter a word: househouse foundEnter a word: houseshouses not fou
12、ndEnter a word: brickbrick foundEnter a word: manlymanly not found 此处最重要的是,我们能在单个函数调用中,串行、反串行化任意大小、任意复杂性的对象。处理多个句柄当我们传递一个对象的句柄给 Serialize 时,似乎会在底层对对象进行一个复制,那么,实际情况真的是这样吗?假设我们把包含有多个句柄的一个对象写入到其他对象中,或者我们调用 Serialize 两次,每次都给它同一个对象的句柄呢?我们真的想得到同一对象的多个副本吗?在例 4 中演示了这个过程:例 4:using namespace System;using name
13、space System:IO;using namespace System:Runtime:Serialization:Formatters:Binary;/*1*/ Serializable ref class Employee /* . */;int main() Employee emp1 = gcnew Employee();Employee emp2 = gcnew Employee();Employee emp3 = emp2;/*2a*/ Console:WriteLine(“emp1 = emp2 is 0“, (emp1 = emp2);/*2b*/ Console:Wri
14、teLine(“emp2 = emp3 is 0“, (emp2 = emp3);/*2c*/ Console:WriteLine(“emp1 = emp3 is 0“, (emp1 = emp3);array list = gcnew array(2);list0 = emp1;list1 = list0;/*2d*/ Console:WriteLine(“list0 = list1 is 0“, (list0 = list1);/*2e*/ Console:WriteLine(“list0 = emp1 is 0“, (list0 = emp1);/*2f*/ Console:WriteL
15、ine(“list1 = emp1 is 0“, (list1 = emp1);/将数据串行化到文件BinaryFormatter formatter = gcnew BinaryFormatter;Stream file = File:Open(“Sr03.ser“, FileMode:Create);/*3a*/ formatter-Serialize(file, emp1);/*3b*/ formatter-Serialize(file, emp2);/*3c*/ formatter-Serialize(file, emp3);/*3d*/ formatter-Serialize(fil
16、e, list);file-Close();/从文件中反串行化数据-即读取数据file = File:Open(“Sr03.ser“, FileMode:Open);/*4a*/ emp1 = static_cast(formatter-Deserialize(file);/*4b*/ emp2 = static_cast(formatter-Deserialize(file);/*4c*/ emp3 = static_cast(formatter-Deserialize(file);/*4d*/ list = static_cast(formatter-Deserialize(file);f
17、ile-Close();/*5a*/ Console:WriteLine(“emp1 = emp2 is 0“, (emp1 = emp2);/*5b*/ Console:WriteLine(“emp2 = emp3 is 0“, (emp2 = emp3);/*5c*/ Console:WriteLine(“emp1 = emp3 is 0“, (emp1 = emp3);/*5d*/ Console:WriteLine(“list0 = list1 is 0“, (list0 = list1);/*5e*/ Console:WriteLine(“list0 = emp1 is 0“, (l
18、ist0 = emp1);/*5f*/ Console:WriteLine(“list1 = emp1 is 0“, (list1 = emp1); 在本例中,我们想对 Employee 类型(在标记 1 中的用户自定义类型)的对象进行串行化,必须把 Serializable 属性附加到这个类型上。如果我们试图串行化一个没有标明此属性的类对象,将会抛出一个 System:Runtime:Serialization:SerializationException 类型的异常。串行化之前的程序输出如插 3 所示:插 3:串行化之前例 4 的输出emp1 = emp2 is Falseemp2 = e
19、mp3 is Trueemp1 = emp3 is Falselist0 = list1 is Truelist0 = emp1 is Truelist1 = emp1 is True 我们对四个目标进行了串行化,前两个代表了不同的 Employee 对象,而第三个是对第二个的引用,第四个为包含两个元素的数组,这两个元素均引用第一个 Employee 对象。程序的输出表明了它们之间的这些关系,反串行化之后的输出见插 4:插 4:反串行化之后例 4 的输出emp1 = emp2 is Falseemp2 = emp3 is Falseemp1 = emp3 is Falselist0 = lis
20、t1 is Truelist0 = emp1 is Falselist1 = emp1 is False 注意,现在第三个 Employee 句柄已不再是一个指向第二个 Employee 对象的句柄了,类似地,尽管 list0与 list1都引用同一个 Empolyee 对象,但对象已不是我们取回的第一个对象了。在此应看到,当多个对象逐个串行化之后,它们是相关联的,而当反串行化之后,它们的关系并没有因此而恢复,但是,对象内部的关系仍然被维持。自定义的串行化默认情况下,当一个对象被串行化时,所有的非静态实例字段都会被写入,并在反串行化期间顺序读回;然而,对包含静态字段的类,这可能会导致一个问题。
21、在例 5 中使用了 Point 类,其不但包含了用于追踪每个 Point x 与 y 坐标的实例变量,而且还会跟踪在程序执行期间创建的 Point 数目。例如,在例 5 中,通过显示构造函数调用,创建了 4 个 Point,并将它们串行化到磁盘;当它们被反串行化时,又创建了 4 个新的Point,因此 Point 总数现在为 8,插 5 中是程序的输出:例 5:using namespace System;using namespace System:IO;using namespace System:Runtime:Serialization:Formatters:Binary;int ma
22、in() Console:WriteLine(“PointCount: 0“, Point:PointCount);Point p1 = gcnew Point(15, 10);Point p2 = gcnew Point(-2, 12);array p3 = gcnew Point(18, -5), gcnew Point(25, 19);Console:WriteLine(“PointCount: 0“, Point:PointCount);BinaryFormatter formatter = gcnew BinaryFormatter;Stream file = File:Open(“
23、Point.ser“, FileMode:Create);formatter-Serialize(file, p1);formatter-Serialize(file, p2);formatter-Serialize(file, p3);file-Close();file = File:Open(“Point.ser“, FileMode:Open);Point p4 = static_cast(formatter-Deserialize(file);Console:WriteLine(“PointCount: 0“, Point:PointCount);Point p5 = static_c
24、ast(formatter-Deserialize(file);Console:WriteLine(“PointCount: 0“, Point:PointCount);array p6 = static_cast(formatter-Deserialize(file);Console:WriteLine(“PointCount: 0“, Point:PointCount);file-Close();Console:WriteLine(“p1: 0, p4: 1“, p1, p4);Console:WriteLine(“p2: 0, p5: 1“, p2, p5);Console:WriteL
25、ine(“p30: 0, p60: 1“, p30, p60);Console:WriteLine(“p31: 0, p61: 1“, p31, p61); 插 5:反串行化创建了 4 个新的 PointPointCount: 0PointCount: 4PointCount: 5PointCount: 6PointCount: 8p1: (15,10), p4: (15,10)p2: (-2,12), p5: (-2,12)p30: (18,-5), p60: (18,-5)p31: (25,19), p61: (25,19) 当调用 Point 类的公有构造函数来构造一个新对象时,Poin
26、t 计数字段也会相应增长;而当我们反串行化一个或多个 Point 时,问题发生了,对 Point 的 Deserialize 调用实际上创建了一个新的 Point 对象,但它并没有为这些对象调用任何的构造函数啊。另外要明确一点,即使新 Point 数被增量 1 ,PointCount 也不会自动增长。我们可重载由接口ISerializable(从 System:Runtime:Serialization)实现的默认的串行与反串行动作;这个接口需要定义一个调用 GetObjectData 的函数,这个函数就可以允许我们重载串行化过程。GetObjectData 函数的目的是,以串行化一个父类对象
27、所需的数据,增加一个SerializationInfo 对象,在此,名称、值、类型信息都被提供给 AddValue 函数,并由对象作为第二个参数。名称字符串可为任意,只要它在这种类型的串行化中唯一就行了。 (如果使用了两个相同的名称,会抛出 SerializationException 异常。 )如果要重载反串行化过程,必须定义另一个构造函数,注意这个构造为私有类型,因为它只会被反串行化机制所调用,没有从外部访问的必要。串行化的格式以上所有串行化的例子当中,我们使用了 BinaryFormatter 类型,其以某种能被高效地处理的压缩格式来存储数据;然而,其他格式也能做到这点,例如,可使用一个SOAP,SOAP(Simple Object Access Protocol-简单对象访问协议)是一种用于在 Web 上交换结构化及类型信息的简单的、基于 XML 的协议,该协议未包含任何应用程序或传输语义,所以它具有高度模块化及扩展性的特点。当然,大家也能创建其他的自定义格式。