DotNet中的序列化方法有三种:XML 序列化、SOAP 序列化和二进制序列化。若是序列化到文件的话,前两者生成的是 XML 文件,二进制序列化生成二进制文件。
跟序列化相关的两个类型:
SerializableAttribute:指示一个类是可以序列化的。
ISerializable:使对象可以自己控制其序列化和反序列化的过程。
XML序列化的优点是使用简单,也颇具灵活性,比如可以控制数据在 XML 文件中是作为 Element 还是作为 Attribute ,以及显示的名称等。XML 序列化对一般的应用是足以应付的,但当 序列化循环引用的对象,即有多个引用指向同一个对象实体时,XML 序列化机制将在每个引用的地方都创建一个对象副本。这除了会导致数据存储上的冗余外,更严重的是使一个对象在反序列化后变成了毫无关系的多个对象,即 XML 反序列化后可能得到错误的对象关系图表。比如下图所示的简单例子:
对应三种类型分别有三个对象 cObject 及其成员 _aObject、 _bObject,_aObject 由 cObject 构造,_bObject 中存的是对 _aObject 的引用,即 cObject 的成员 _aObject 和 _bObject 的成员 _aObject 是同一个东西。则 ClassC 类型的对象 cObject 经 XML 序列化的结果是:
从这个结果反序列化后得到的新的 cObject,其成员 _aObject 跟 _bObject 中的 _aObject 可就是两个对象了。要解决这个问题,我能想到的就是给对象加上 GUID 属性,在反序列化后根据 GUID 属性重新设置引用,不知还有没有其它办法。
SOAP 和二进制序列化的优点是可以精确地控制序列化及反序列化的过程,并可以序列化对象的非公共成员。所以对复杂对象的序列化,我们应该在实现 ISerializable 接口后,用 SOAP 或 二进制的方式保存数据。至于缺点,如果你嫌在类名上加个 Serializable 标记很麻烦的话,这也许算个缺点。
还是上面的例子,如果用 SOAP 序列化 cObject 对象,结果是:
很明显,里面存的是对象引用,这是一个精确副本,反序列化后毫无问题。
附:ClassC 的一段代码:
跟序列化相关的两个类型:
SerializableAttribute:指示一个类是可以序列化的。
ISerializable:使对象可以自己控制其序列化和反序列化的过程。
列表比较三种序列化方法。
XML | SOAP | 二进制 | |
序列化器类 | XmlSerializer | SoapFormatter | BinaryFormatter |
SerializableAttribute 标记 | 不需要 | 需要 | |
ISerializable 接口 | 不需要实现,实现了也不起作用。 | 可以不实现,但实现了就起作用。 | |
无参构造函数 | 必须有,系统提供的缺省无参构造函数也算。 | 不需要,因为反序列化时不调用构造函数。 | |
被序列化的数据成员 | 公共属性和字段 | 所有 | |
产生文件大小 | 大 | 大 | 小 |
XML序列化的优点是使用简单,也颇具灵活性,比如可以控制数据在 XML 文件中是作为 Element 还是作为 Attribute ,以及显示的名称等。XML 序列化对一般的应用是足以应付的,但当 序列化循环引用的对象,即有多个引用指向同一个对象实体时,XML 序列化机制将在每个引用的地方都创建一个对象副本。这除了会导致数据存储上的冗余外,更严重的是使一个对象在反序列化后变成了毫无关系的多个对象,即 XML 反序列化后可能得到错误的对象关系图表。比如下图所示的简单例子:
对应三种类型分别有三个对象 cObject 及其成员 _aObject、 _bObject,_aObject 由 cObject 构造,_bObject 中存的是对 _aObject 的引用,即 cObject 的成员 _aObject 和 _bObject 的成员 _aObject 是同一个东西。则 ClassC 类型的对象 cObject 经 XML 序列化的结果是:
<
ClassC
xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd
="http://www.w3.org/2001/XMLSchema"
>
< Name > ccc </ Name >
< AObject >
< ID > 37 </ ID >
< Name > aaa </ Name >
</ AObject >
< BObject >
< Name > bbb </ Name >
< AObject >
< ID > 37 </ ID >
< Name > aaa </ Name >
</ AObject >
</ BObject >
</ ClassC >
< Name > ccc </ Name >
< AObject >
< ID > 37 </ ID >
< Name > aaa </ Name >
</ AObject >
< BObject >
< Name > bbb </ Name >
< AObject >
< ID > 37 </ ID >
< Name > aaa </ Name >
</ AObject >
</ BObject >
</ ClassC >
从这个结果反序列化后得到的新的 cObject,其成员 _aObject 跟 _bObject 中的 _aObject 可就是两个对象了。要解决这个问题,我能想到的就是给对象加上 GUID 属性,在反序列化后根据 GUID 属性重新设置引用,不知还有没有其它办法。
SOAP 和二进制序列化的优点是可以精确地控制序列化及反序列化的过程,并可以序列化对象的非公共成员。所以对复杂对象的序列化,我们应该在实现 ISerializable 接口后,用 SOAP 或 二进制的方式保存数据。至于缺点,如果你嫌在类名上加个 Serializable 标记很麻烦的话,这也许算个缺点。
还是上面的例子,如果用 SOAP 序列化 cObject 对象,结果是:
<
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/"
xmlns:clr
="http://schemas.microsoft.com/soap/encoding/clr/1.0"
SOAP-ENV:encodingStyle
="http://schemas.xmlsoap.org/soap/encoding/"
>
< SOAP-ENV:Body >
< a1:ClassC id ="ref-1" xmlns:a1 ="http://schemas.microsoft.com/clr/nsassem/SerializeTest.CrossReference/SerializeTest%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull" >
< ClassCNameName id ="ref-3" > ccc </ ClassCNameName >
< ClassCAObjectAObject href ="#ref-4" />
< ClassCBObjectBObject href ="#ref-5" />
</ a1:ClassC >
< a1:ClassA id ="ref-4" xmlns:a1 ="http://schemas.microsoft.com/clr/nsassem/SerializeTest.CrossReference/SerializeTest%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull" >
< ClassANameName id ="ref-6" > aaa </ ClassANameName >
< ClassAIDID > 37 </ ClassAIDID >
</ a1:ClassA >
< a1:ClassB id ="ref-5" xmlns:a1 ="http://schemas.microsoft.com/clr/nsassem/SerializeTest.CrossReference/SerializeTest%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull" >
< ClassBNameName id ="ref-7" > bbb </ ClassBNameName >
< ClassBAObjectAObject href ="#ref-4" />
</ a1:ClassB >
</ SOAP-ENV:Body >
</ SOAP-ENV:Envelope >
< SOAP-ENV:Body >
< a1:ClassC id ="ref-1" xmlns:a1 ="http://schemas.microsoft.com/clr/nsassem/SerializeTest.CrossReference/SerializeTest%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull" >
< ClassCNameName id ="ref-3" > ccc </ ClassCNameName >
< ClassCAObjectAObject href ="#ref-4" />
< ClassCBObjectBObject href ="#ref-5" />
</ a1:ClassC >
< a1:ClassA id ="ref-4" xmlns:a1 ="http://schemas.microsoft.com/clr/nsassem/SerializeTest.CrossReference/SerializeTest%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull" >
< ClassANameName id ="ref-6" > aaa </ ClassANameName >
< ClassAIDID > 37 </ ClassAIDID >
</ a1:ClassA >
< a1:ClassB id ="ref-5" xmlns:a1 ="http://schemas.microsoft.com/clr/nsassem/SerializeTest.CrossReference/SerializeTest%2C%20Version%3D1.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull" >
< ClassBNameName id ="ref-7" > bbb </ ClassBNameName >
< ClassBAObjectAObject href ="#ref-4" />
</ a1:ClassB >
</ SOAP-ENV:Body >
</ SOAP-ENV:Envelope >
很明显,里面存的是对象引用,这是一个精确副本,反序列化后毫无问题。
附:ClassC 的一段代码:
public
ClassC()
{
_name = "Unknown ClassC Object";
InitData();
}
private void InitData()
{
_aObject = new ClassA( 1 );
_aObject.Name = "aaa";
_aObject.ID = 37;
_bObject = new ClassB();
_bObject.Name = "bbb";
_bObject.AObject = _aObject;
}
{
_name = "Unknown ClassC Object";
InitData();
}
private void InitData()
{
_aObject = new ClassA( 1 );
_aObject.Name = "aaa";
_aObject.ID = 37;
_bObject = new ClassB();
_bObject.Name = "bbb";
_bObject.AObject = _aObject;
}