XmlSerializer, DataContractSerializer 和 BinaryFormatter区别与用法分析
.NET提供了很多序列化对象的方法,了解他们之间的区别才能更好地确定使用哪一种序列化方式并正确地使用。本文从下面几个方面对标题中的三种序列化方法进行了分析。
- 范围:Property Or Field Or Both
- 可见性:Public or Private Or All
- 可访问性:Readonly Property
- 回调:指OnSerializing, OnSerialized, OnDeserializing, OnDeserialized这些回调。
- 包含循环引用的对象
- 包含Dictionary的对象
- 乱序读(XML)
- 自定义序列化结果。
- 对被序列化的类有没有什么Attribute要求。有时被序列化的对象位于第三方库中,无法修改源代码,如果要序列化这样的对象,就不能使用一定要有Attribute的Serializer。
先把结果整理出来再分析:代码可以从这里下载,所写测试可能并不全面,所以如果有什么错误,欢迎大家指出,不然要误人子弟了。不过有这篇更误人子弟的文章殿底(Google top 1),我相信这个表不会更糟。
XmlSerializer | DataContractSerializer | BinaryFormatter | |
范围 | Both | Both | Field Only |
可见性 | Public Only | All | All |
只读属性 | 不支持 | 支持,但要加Attribute | N/A |
回调支持 | 不支持 | 支持 | 支持 |
循环引用 | 不支持 | 支持,但要加Attribute | 支持 |
Dictionary | 不支持 | 支持,专有格式 | 支持 |
乱序读 | 支持 | 不支持 | 不支持 |
自定义格式 | 支持,要加Attribute | 不支持Xml Attribute | 通过接口 |
Attribute要求 | 不必要 | 不必要 | 必须有Serializable |
有几点需要解释一下。
性能?大小?
在我写的几个测试用例中,无论对于简单对象,还是嵌套对象,还是数组重复引用对象,这几个Serializer的性能和大小基本在一个数量级上。他们之间功能上的差异远大于他们性能上的差异。这个时候,使用哪个方法的决策应该取决于功能及其约束,而不是性能。如果你在意性能,应该用第三方库,如Google的protobuf-net。无论从性能、序列化结果的大小还是兼容性上,protobuf-net比这三者都要优秀,当然,not human readable。
Attribute要求
不少同类文章都声称XmlSerializer和DataContractSerializer都要求在类上加相应的Attribute。但事实上并不是这样。只有当你想要改变默认的序列化结果时,Attribute才是必要的。
最常见的一个错误就是,给XmlSerializer要用到的类加Serializable Attribute。这个Attribute其实是给BinaryFormatter和SoapFormatter用的,一般用在Remoting和Messaging上。
乱序读
乱序读的意思是,Deserialize的过程中,不对Xml节点的顺序做要求。Xml本身就是一种标记性的,带有声明性的语言,多数基于Xml的网络协议都不会对Xml节点的顺序做要求。比如常见的RSS,ATOM,OPDS等。
WCF使用的DataContractSerializer要求Xml节点顺序要么严格指定,要么按字母顺序排列,或许有性能上考虑,但是WCF的定位才是允许DataContractSerializer严格要求Xml节点顺序的根本原因。WCF本身是用于通信的,其对序列化的定位,仅仅只是消息传递的手段,消息格式的严格定义并没有什么不好,而且调用方的代码都可以通过WSDL来生成,自然也就更不会出现解析时的问题。所以不支持乱序读完全是由其功能设定决定的。
XmlSerializer,在我看来可以做为Object Xml Mapping的工具,当然要尽可能支持所有Xml的标准。
自定义的支持
XmlSerializer和DataContractSerializer都支持IXmlSerializer接口。通过这个接口DataContractSerializer甚至可以支持Attribute,但是,这其实已经不是在使用DataContractSerializer的序列化机制了。而且在实际项目中也不具备可操作性。所以在上表中,还是把DataContractSerializer标记为不支持Xml Attribute。
还有一些常见的接口,如ISerializable和IDeserializationCallback是给BinaryFormatter用的,以支持自定义序列化的过程。这里我很奇怪为了BinaryFormatter已经支持了OnDeserialized Attribute,为什么还有这么个接口来提供相似的功能。
一些小的细节
两个Xml序列化方法还有一些小的挺有意思的差别,我试图猜测产生这种差异的原因,却没想出来,或许是一些微软员工不为人知的设计理念,也许只是写代码时的顺心而为罢了。
- XmlSerializer不序列化Null值,DataContractSerializer默认会序列化Null值。
- DataContractSerializer与Attribute:对没有任何Attribute的类,序列化所有Public的可读可写Property和Field;对仅仅加了Serializable Attribute的类,序列化所有可见性的Field(为了和BinaryFormatter行为一致吗);如果仅仅加了DataContract Attribute,则什么都不会序列化出来,一定要加DataMember。
- DataContract支持序列只读属性,但是属性上要加DataMember.
- DataContractSerializer,仅仅加DataMember而不加DataContract会出异常。
- DataContractSerializer与ISerializable接口不兼容,直接抛异常。
- DataContractSerializer支持IXmlSerializer接口,但是实现了这个接口,就不能加DataContract了。否则抛异常。这是什么道理?
- XmlSerializer支持乱序读的代价是,你不能控制你自己的生成的Xml的节点的顺序。也就是说ElementName Attribute中不能指定Order。
- BinaryFormatter要求被序列化的类必须“满城尽带Serializable”。所以如果你改不了源代码,序列化不了就是序列化不了。而XmlSerializable,你尽可以通过继承的方式,把Internal和Protected的Property序列化出来。
小结
基于上面的客观结果,相信你已经有了自己的判断。我个人意见,DataContractSerializable就用在它应该用的地方吧,如果不是用WCF,还是不要用它了,它的序列化结果有一些微软专属的东西。对于来自网络的松散Xml接口数据,XmlSerializer是不二之选。如果想把对象完整地保存下来(数据与状态),同时又不需要被人看。那就用BinaryFormatter吧。如果对性能或是数据大小要求比较高,那这三个就都能用,用protobuff吧。
补充
这些信息算是一种经验,但是绝对不适合作面试或是笔试题用。
2012年6月2日:
发现一个不错的总结: