[原创]WCF技术剖析之十三:序列化过程中的已知类型(Known Type)

[爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道《天天山海经》为此录制的节目视频(苏州话)]]DataContractSerializer承载着所有数据契约对象的序列化和反序列化操作。在上面一篇文章(《数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)》)中,我们谈到DataContractSerializer基本的序列化规则;如何控制DataContractSerializer序列化或者反序列化对象的数量;以及如何在序列化后的XML中保存被序列化对象的对象引用结构。在这篇文章中,我们会详细讨论WCF序列化中一个重要的话题:已知类型(Known Type)。

WCF下的序列化与反序列化解决的是数据在两种状态之间的相互转化:托管类型对象和XML。由于类型定义了对象的数据结构,所以无论对于序列化还是反序列化,都必须事先确定对象的类型。如果被序列化对象或者被反序列化生成的对象包含不可知的类型,序列化或者反序列化将会失败。为了确保DataContractSerializer的正常序列化和反序列化,我们需要将“未知”类型加入DataContractSerializer“已知”类型列表中。

一、未知类型导致序列化失败

.NET的类型可以分为两种:声明类型和真实类型。我们提倡面向接口的编程,对象的真实类型往往需要在运行时才能确定,在编程的时候往往只需要指明类型的声明类型,比如类型实现的接口或者抽象类。当我们使用基于接口或者抽象类创建的DataContractSerializer去序列化一个实现了该接口或者继承该抽象类的实例的时候,往往会因为对对象的真实类型无法识别造成不能正常地序列化。比如下面的代码中,我们定义了3个类型,一个接口、一个抽象类和一个具体类。

   1: namespace Artech.DataContractSerializerDemos
<!--CRLF-->
   2: {
<!--CRLF-->
   3:     public interface IOrder
<!--CRLF-->
   4:     {
<!--CRLF-->
   5:         Guid ID
<!--CRLF-->
   6:         { get; set; }
<!--CRLF-->
   7: 
<!--CRLF-->
   8:         DateTime Date
<!--CRLF-->
   9:         { get; set; }
<!--CRLF-->
  10: 
<!--CRLF-->
  11:         string Customer
<!--CRLF-->
  12:         { get; set; }
<!--CRLF-->
  13: 
<!--CRLF-->
  14:         string ShipAddress
<!--CRLF-->
  15:         { get; set; }
<!--CRLF-->
  16:     }
<!--CRLF-->
  17: 
<!--CRLF-->
  18:     [DataContract]
<!--CRLF-->
  19:     public abstract class OrderBase : IOrder
<!--CRLF-->
  20:     {
<!--CRLF-->
  21:         [DataMember]
<!--CRLF-->
  22:         public Guid ID
<!--CRLF-->
  23:         { get; set; }
<!--CRLF-->
  24: 
<!--CRLF-->
  25:         [DataMember]
<!--CRLF-->
  26:         public DateTime Date
<!--CRLF-->
  27:         { get; set; }
<!--CRLF-->
  28: 
<!--CRLF-->
  29:         [DataMember]
<!--CRLF-->
  30:         public string Customer
<!--CRLF-->
  31:         { get; set; }
<!--CRLF-->
  32: 
<!--CRLF-->
  33:         [DataMember]
<!--CRLF-->
  34:         public string ShipAddress
<!--CRLF-->
  35:         { get; set; }
<!--CRLF-->
  36:     }
<!--CRLF-->
  37: 
<!--CRLF-->
  38:     [DataContract]
<!--CRLF-->
  39:     public class Order : OrderBase
<!--CRLF-->
  40:     {
<!--CRLF-->
  41:         [DataMember]
<!--CRLF-->
  42:         public double TotalPrice
<!--CRLF-->
  43:         { get; set; }
<!--CRLF-->
  44:     }
<!--CRLF-->
  45: }
<!--CRLF-->

当我们通过下面的方式去序列化一个Order对象(注意泛型类型为IOrder或者OrderBase),将会抛出如图1所示SerializationException异常,提示Order类型无法识别。

注:Serialize<T>方法的定义,请参考本系列的上篇文章:《WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)》。

   1: Order order = new Order()
<!--CRLF-->
   2: {
<!--CRLF-->
   3:     ID = Guid.NewGuid(),
<!--CRLF-->
   4:     Customer = "NCS",
<!--CRLF-->
   5:     Date = DateTime.Today,
<!--CRLF-->
   6:     ShipAddress = "#328, Airport Rd, Industrial Park, Suzhou Jiangsu Province",
<!--CRLF-->
   7:     TotalPrice = 8888.88
<!--CRLF-->
   8: };
<!--CRLF-->
   9: 
<!--CRLF-->
  10: Serialize<IOrder>(order, @"E:\order.xml");
<!--CRLF-->
  11: //或者
<!--CRLF-->
  12: Serialize<OrderBase>(order, @"E:\order.xml");
<!--CRLF-->

clip_image002

图1 “未知”类型导致的序列化异常

二、DataContractSerializer的已知类型集合

解决上面这个问题的唯一途径就是让DataContractSerializer能够识别Order类型,成为DataContractSerializer的已知类型(Known Type)。DataContractSerializer内部具有一个已知类型的列表,我们只需要将Order的类型添加到这个列表中,就能从根本上解决这个问题。通过下面6个重载构造函数中的任意一个,均可以通过knownTypes参数指定DataContractSerializer的已知类型集合,该集合最终反映在DataContractSerializer的制度属性KnownTypes上。

   1: public sealed class DataContractSerializer : XmlObjectSerializer
<!--CRLF-->
   2: {
<!--CRLF-->
   3:     public DataContractSerializer(Type type, IEnumerable<Type> knownTypes);
<!--CRLF-->
   4:     public DataContractSerializer(Type type, string rootName, string rootNamespace, IEnumerable<Type> knownTypes);
<!--CRLF-->
   5:     public DataContractSerializer(Type type, XmlDictionaryString rootName, XmlDictionaryString rootNamespace, IEnumerable<Type> knownTypes);
<!--CRLF-->
   6:     public DataContractSerializer(Type type, IEnumerable<Type> knownTypes, int maxItemsInObjectGraph, bool ignoreExtensionDataObject, bool preserveObjectReferences, IDataContractSurrogate dataContractSurrogate);
<!--CRLF-->
   7:     public DataContractSerializer(Type type, string rootName, string rootNamespace, IEnumerable<Type> knownTypes, int maxItemsInObjectGraph, bool ignoreExtensionDataObject, bool preserveObjectReferences, IDataContractSurrogate dataContractSurrogate);
<!--CRLF-->
   8:     public DataContractSerializer(Type type, XmlDictionaryString rootName, XmlDictionaryString rootNamespace, IEnumerable<Type> knownTypes, int maxItemsInObjectGraph, bool ignoreExtensionDataObject, bool preserveObjectReferences, IDataContractSurrogate dataContractSurrogate);
<!--CRLF-->
   9:     
<!--CRLF-->
  10:     public ReadOnlyCollection<Type> KnownTypes { get; }
<!--CRLF-->
  11: }
<!--CRLF-->

为了方便后面的演示,我们对我们使用的泛型服务方法Serialize<T>为已知类型作相应的修正,通过第3个参数指定DataContractSerializer的已知类型列表。

   1: public static void Serialize<T>(T instance, string fileName, IList<Type> konwnTypes)
<!--CRLF-->
   2: {
<!--CRLF-->
   3:     DataContractSerializer serializer = new DataContractSerializer(typeof(T), konwnTypes, int.MaxValue, false, false, null);
<!--CRLF-->
   4:     using (XmlWriter writer = new XmlTextWriter(fileName, Encoding.UTF8))
<!--CRLF-->
   5:     {
<!--CRLF-->
   6:         serializer.WriteObject(writer, instance);
<!--CRLF-->
   7:     }
<!--CRLF-->
   8:     Process.Start(fileName);
<!--CRLF-->
   9: }
<!--CRLF-->

三、基于接口的序列化

DataContractSerializer的创建必须基于某个确定的类型,这里的类型既可以是接口,也可以是抽象类或具体类。不过基于接口的DataContractSerializer与基于抽象数据契约类型的DataContractSerializer,在进行序列化时表现出来的行为是不相同的。

在下面的代码中,在调用Serialize<T>的时候,将泛型类型分别设定为接口IOrder和抽象类OrderBase。虽然是对同一个Order对象进行序列化,但是序列化生成的XML却各有不同。文件order.interface.xml的根节点为<z:anyType>,这是因为DataContractAttribute不能应用于接口上面,所以接口不具有数据契约的概念。<z:anyType>表明能够匹配任意类型,相当于类型object。

   1: Order order = new Order()
<!--CRLF-->
   2: {
<!--CRLF-->
   3:     ID = Guid.NewGuid(),
<!--CRLF-->
   4:     Customer = "NCS",
<!--CRLF-->
   5:     Date = DateTime.Today,
<!--CRLF-->
   6:     ShipAddress = "#328, Airport Rd, Industrial Park, Suzhou Jiangsu Province",
<!--CRLF-->
   7:     TotalPrice = 8888.88
<!--CRLF-->
   8: };
<!--CRLF-->
   9: 
<!--CRLF-->
  10: Serialize<IOrder>(order, @"E:\order.interface.xml", new List<Type>{typeof(Order)});
<!--CRLF-->
  11: Serialize<OrderBase>(order, @"E:\order.class.xml", new List<Type> { typeof(Order) });
<!--CRLF-->
   1: <z:anyType xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:d1p1="http://schemas.datacontract.org/2004/07/Artech.DataContractSerializerDemos" i:type="d1p1:Order" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
<!--CRLF-->
   2:     <d1p1:Customer>NCS</d1p1:Customer>
<!--CRLF-->
   3:     <d1p1:Date>2008-12-04T00:00:00+08:00</d1p1:Date>
<!--CRLF-->
   4:     <d1p1:ID>04c07e41-6302-48d1-ac06-87ebbff2b75f</d1p1:ID>
<!--CRLF-->
   5:     <d1p1:ShipAddress>#328, Airport Rd, Industrial Park, Suzhou Jiangsu Province</d1p1:ShipAddress>
<!--CRLF-->
   6:     <d1p1:TotalPrice>8888.88</d1p1:TotalPrice>
<!--CRLF-->
   7: </z:anyType>
<!--CRLF-->
   1: <OrderBase xmlns:i="http://www.w3.org/2001/XMLSchema-instance" i:type="Order" xmlns="http://schemas.datacontract.org/2004/07/Artech.DataContractSerializerDemos">
<!--CRLF-->
   2:     <Customer>NCS</Customer>
<!--CRLF-->
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值