C# 序列化过程中的已知类型(Known Type)
2015-07-03 10:16
483 查看
一、未知类型导致序列化失败
.NET的类型可以分为两种:声明类型和真实类型。我们提倡面向接口的编程,对象的真实类型往往需要在运行时才能确定,在编程的时候往往只需要指明类型的声明类型,比如类型实现的接口或者抽象类。当我们使用基于接口或者抽象类创建的DataContractSerializer去序列化一个实现了该接口或者继承该抽象类的实例的时候,往往会因为对对象的真实类型无法识别造成不能正常地序列化。比如下面的代码中,我们定义了3个类型,一个接口、一个抽象类和一个具体类。
namespaceArtech.DataContractSerializerDemos
{
publicinterfaceIOrder
{
GuidID
{get;set;}
DateTimeDate
{get;set;}
stringCustomer
{get;set;}
stringShipAddress
{get;set;}
}
[DataContract]
publicabstractclassOrderBase:IOrder
{
[DataMember]
publicGuidID
{get;set;}
[DataMember]
publicDateTimeDate
{get;set;}
[DataMember]
publicstringCustomer
{get;set;}
[DataMember]
publicstringShipAddress
{get;set;}
}
[DataContract]
publicclassOrder:OrderBase
{
[DataMember]
publicdoubleTotalPrice
{get;set;}
}
}
当我们通过下面的方式去序列化一个Order对象(注意泛型类型为IOrder或者OrderBase),将会抛出如图1所示SerializationException异常,提示Order类型无法识别。
注:Serialize<T>方法的定义,请参考本系列的上篇文章:《
Orderorder=newOrder()
{
ID=Guid.NewGuid(),
Customer="NCS",
Date=DateTime.Today,
ShipAddress="#328,AirportRd,IndustrialPark,SuzhouJiangsuProvince",
TotalPrice=8888.88
};
Serialize<IOrder>(order,@"E:\order.xml");
//或者
Serialize<OrderBase>(order,@"E:\order.xml");
图1“未知”类型导致的序列化异常
二、DataContractSerializer的已知类型集合
解决上面这个问题的唯一途径就是让DataContractSerializer能够识别Order类型,成为DataContractSerializer的已知类型(KnownType)。DataContractSerializer内部具有一个已知类型的列表,我们只需要将Order的类型添加到这个列表中,就能从根本上解决这个问题。通过下面6个重载构造函数中的任意一个,均可以通过knownTypes参数指定DataContractSerializer的已知类型集合,该集合最终反映在DataContractSerializer的制度属性KnownTypes上。
publicsealedclassDataContractSerializer:XmlObjectSerializer
{
publicDataContractSerializer(Typetype,IEnumerable<Type>knownTypes);
publicDataContractSerializer(Typetype,stringrootName,stringrootNamespace,IEnumerable<Type>knownTypes);
publicDataContractSerializer(Typetype,XmlDictionaryStringrootName,XmlDictionaryStringrootNamespace,IEnumerable<Type>knownTypes);
publicDataContractSerializer(Typetype,IEnumerable<Type>knownTypes,intmaxItemsInObjectGraph,boolignoreExtensionDataObject,boolpreserveObjectReferences,IDataContractSurrogatedataContractSurrogate);
publicDataContractSerializer(Typetype,stringrootName,stringrootNamespace,IEnumerable<Type>knownTypes,intmaxItemsInObjectGraph,boolignoreExtensionDataObject,boolpreserveObjectReferences,IDataContractSurrogatedataContractSurrogate);
publicDataContractSerializer(Typetype,XmlDictionaryStringrootName,XmlDictionaryStringrootNamespace,IEnumerable<Type>knownTypes,intmaxItemsInObjectGraph,boolignoreExtensionDataObject,boolpreserveObjectReferences,IDataContractSurrogatedataContractSurrogate);
publicReadOnlyCollection<Type>KnownTypes{get;}
}
为了方便后面的演示,我们对我们使用的泛型服务方法Serialize<T>为已知类型作相应的修正,通过第3个参数指定DataContractSerializer的已知类型列表。
publicstaticvoidSerialize<T>(Tinstance,stringfileName,IList<Type>konwnTypes)
{
DataContractSerializerserializer=newDataContractSerializer(typeof(T),konwnTypes,int.MaxValue,false,false,null);
using(XmlWriterwriter=newXmlTextWriter(fileName,Encoding.UTF8))
{
serializer.WriteObject(writer,instance);
}
Process.Start(fileName);
}
三、基于接口的序列化
DataContractSerializer的创建必须基于某个确定的类型,这里的类型既可以是接口,也可以是抽象类或具体类。不过基于接口的DataContractSerializer与基于抽象数据契约类型的DataContractSerializer,在进行序列化时表现出来的行为是不相同的。
在下面的代码中,在调用Serialize<T>的时候,将泛型类型分别设定为接口IOrder和抽象类OrderBase。虽然是对同一个Order对象进行序列化,但是序列化生成的XML却各有不同。文件order.interface.xml的根节点为<z:anyType>,这是因为DataContractAttribute不能应用于接口上面,所以接口不具有数据契约的概念。<z:anyType>表明能够匹配任意类型,相当于类型object。
Orderorder=newOrder()
{
ID=Guid.NewGuid(),
Customer="NCS",
Date=DateTime.Today,
ShipAddress="#328,AirportRd,IndustrialPark,SuzhouJiangsuProvince",
TotalPrice=8888.88
};
Serialize<IOrder>(order,@"E:\order.interface.xml",newList<Type>{typeof(Order)});
Serialize<OrderBase>(order,@"E:\order.class.xml",newList<Type>{typeof(Order)});
<z:anyTypexmlns: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/">
<d1p1:Customer>NCS</d1p1:Customer>
<d1p1:Date>2008-12-04T00:00:00+08:00</d1p1:Date>
<d1p1:ID>04c07e41-6302-48d1-ac06-87ebbff2b75f</d1p1:ID>
<d1p1:ShipAddress>#328,AirportRd,IndustrialPark,SuzhouJiangsuProvince</d1p1:ShipAddress>
<d1p1:TotalPrice>8888.88</d1p1:TotalPrice>
</z:anyType>
<OrderBasexmlns:i="http://www.w3.org/2001/XMLSchema-instance"i:type="Order"xmlns="http://schemas.datacontract.org/2004/07/Artech.DataContractSerializerDemos">
<Customer>NCS</Customer>
<Date>2008-12-04T00:00:00+08:00</Date>
<ID>04c07e41-6302-48d1-ac06-87ebbff2b75f</ID>
<ShipAddress>#328,AirportRd,IndustrialPark,SuzhouJiangsuProvince</ShipAddress>
<TotalPrice>8888.88</TotalPrice>
</OrderBase>
实际上,在WCF应用中,如果服务契约的操作的参数定义为接口,在发布出来的元数据中,接口类型就相当于object,并且当客户端通过添加服务引用生成客户端服务契约的时候,相应的参数类型就是object类型。比如对于下面的服务契约的定义,当客户端导出后将变成后面的样式。
[ServiceContract(Namespace="http://www.artech.com/")]
publicinterfaceIOrderManager
{
[OperationContract]
voidProcessOrder(IOrderorder);
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel","3.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="ServiceReferences.IOrderManager")]
publicinterfaceIOrderManager
{
[System.ServiceModel.OperationContractAttribute(Action="http://www.artech.com/IOrderManager/ProcessOrder",ReplyAction="http://www.artech.com/IOrderManager/ProcessOrderResponse")]
voidProcessOrder(objectorder);
}
四、KnownTypeAttribute与ServiceKnownTypeAttribute
对于已知类型,可以通过两个特殊的自定义特性进行设置:KnownTypeAttribute和ServiceKnownTypeAttribute。KnownTypeAttribute应用于数据契约中,用于设置继承与该数据契约类型的子数据契约类型,或者引用的其他潜在的类型。ServiceKnownTypeAttribute既可以应用于服务契约的接口和方法上,也可以应用在服务实现的类和方法上。应用的目标元素决定了定义的已知类型的作用范围。下面的代码中,在基类OrderBase指定了子类的类型Order。
[DataContract]
[KnownType(typeof(Order))]
publicabstractclassOrderBase:IOrder
{
//省略成员
}
而ServiceKnownTypeAttribute特性,仅可以使用在服务契约类型上,也可以应用在服务契约的操作方法上。如果应用在服务契约类型上,已知类型在所有实现了该契约的服务操作中有效,如果应用于服务契约的操作方法上,则定义的已知类型在所有实现了该契约的服务对应的操作中有效。
[ServiceContract]
[ServiceKnownType(typeof(Order))]
publicinterfaceIOrderManager
{
[OperationContract]
voidProcessOrder(OrderBaseorder);
}
[ServiceContract]
publicinterfaceIOrderManager
{
[OperationContract]
[ServiceKnownType(typeof(Order))]
voidProcessOrder(OrderBaseorder);
}
ServiceKnownTypeAttribute也可以应用于具体的服务类型和方法上面。对于前者,通过ServiceKnownTypeAttribute定义的已知类型在整个服务的所有方法中有效,而对于后者,则已知类型仅限于当前方法。
[ServiceKnownType(typeof(Order))]
publicclassOrderManagerService:IOrderManager
{
publicvoidProcessOrder(OrderBaseorder)
{
//省略成员
}
}
publicclassOrderManagerService:IOrderManager
{
[ServiceKnownType(typeof(Order))]
publicvoidProcessOrder(OrderBaseorder)
{
//省略成员
}
}
除了通过自定义特性的方式设置已知类型外,已知类型还可以通过配置的方式进行指定。已知类型定义在<system.runtime.serialization>配置节中,采用如下的定义方式。这和我们在上面通过KnownTypeAttribute指定Order类型是完全等效的。
<?xmlversion="1.0"encoding="utf-8"?>
<configuration>
<system.runtime.serialization>
<dataContractSerializer>
<declaredTypes>
<addtype="Artech.DataContractSerializerDemos.OrderBase,Artech.DataContractSerializerDemos.KnownTypes">
<knownTypetype="Artech.DataContractSerializerDemos.Order,Artech.DataContractSerializerDemos.KnownTypes"/>
</add>
</declaredTypes>
</dataContractSerializer>
</system.runtime.serialization>
</configuration>
相关文章推荐
- C#域名解析简单实现方法
- C#操作Office.word(三)
- C#操作Office.word(二)
- C#调用CMD
- C#查看各种变量的指针地址
- WPF中使用WinForm控件预览DWG文件(学习笔记)
- c#system.InvalidOperation 当传递已修改行的datarow集合时,更新要求有效的 UpdateCommand。
- C#实现进程管理的启动和停止实例
- C#——依赖接口编程与简单工厂
- C#中的AssemblyInfo 程序集信息
- C#进程监控方法实例分析
- C#命令模式用法实例
- C# wpf 列出文件夹所有文件
- 白话C#:特性(转)
- C#引用第三方DLL时报无法引用此类名错误
- C#增删改查
- C#验证码
- C# 标识类
- C# Unicode转义(\uXXXX)解码
- 浅析EF涉及的一些C#语言特性