数据协定已知类型
KnownTypeAttribute 类允许您预先指定应该在反序列化期间包括在考虑范围内的类型。 有关工作示例,请参阅 Known Types 示例。
通常,在客户端和服务之间传递参数和返回值时,这两个终结点共享要传输的数据的所有数据协定。 但是,在以下情况下并非如此:
已发送的数据协定派生自预期的数据协定。 有关详细信息,请参阅数据协定等效性中有关继承的一节。 在该情况下,传输的数据没有与接收终结点所预期相同的数据协定。
要传输的信息的声明类型是接口,而非类、结构或枚举。 因此,无法预先知道实际发送了实现接口的哪个类型,接收终结点就无法预先确定已传输数据的数据协定。
要传输的信息的声明类型是 Object。 由于每个类型都继承自 Object,而且无法预先知道实际发送了哪个类型,因此接收终结点无法预先确定已传输数据的数据协定。 这是第一个项的特殊情况:每个数据协定都源自为 Object生成的默认空数据协定。
某些类型(包括 .NET Framework 类型)具有属于上述三种类别之一的成员。 例如, Hashtable 使用 Object 在哈希表中存储实际对象。 在序列化这些类型时,接收方无法预先确定这些成员的数据协定。
KnownTypeAttribute 类
数据到达接收终结点时,WCF 运行时会尝试将数据反序列化为公共语言运行时 (CLR) 类型的实例。 通过首先检查传入消息选择为反序列化而实例化的类型,以确定消息内容遵循的数据协定。 然后反序列化引擎尝试查找实现与消息内容兼容的数据协定的 CLR 类型。 反序列化引擎在此过程中允许的侯选类型集称为反序列化程序的“已知类型”集。
让反序列化引擎了解某个类型的一种方法是使用 KnownTypeAttribute。 不能将属性应用于单个数据成员,只能将它应用于整个数据协定类型。 将属性应用于可能为类或结构的“外部类型” 。 在其最基本的用法中,应用属性会将类型指定为“已知类型”。只要反序列化外部类型的对象或通过其成员引用的任何对象,就会导致已知类型成为已知类型集的一部分。 可以将多个 KnownTypeAttribute 属性应用于同一类型。
已知类型和基元
基元类型以及被视为基元的某些类型(例如, DateTime 和 XmlElement)始终是“已知”的,且从来不必通过此机制进行添加。 但是,必须显式添加基元类型的数组。 大多数集合被视为等效于数组。 (非泛型集合被视为等效于 Object的数组)。 有关使用基元、基元数组和基元集合的示例,请参见示例 4。
注意
与其他基元类型不同, DateTimeOffset 结构默认情况下不是已知类型,因此必须将它手动添加到已知类型列表。
示例
下面的示例说明如何使用 KnownTypeAttribute 类。
示例 1
有三个具有继承关系的类。
[DataContract]
public class Shape { }
[DataContract(Name = "Circle")]
public class CircleType : Shape { }
[DataContract(Name = "Triangle")]
public class TriangleType : Shape { }
<DataContract()> _
Public Class Shape
End Class
<DataContract(Name:="Circle")> _
Public Class CircleType
Inherits Shape
End Class
<DataContract(Name:="Triangle")> _
Public Class TriangleType
Inherits Shape
End Class
如果 CompanyLogo
成员设置为 ShapeOfLogo
或 CircleType
对象,则可以序列化下面的 TriangleType
类,而不能对其进行反序列化,因为反序列化引擎无法识别具有数据协定名称“Circle”或“Triangle”的任何类型。
[DataContract]
public class CompanyLogo
{
[DataMember]
private Shape ShapeOfLogo;
[DataMember]
private int ColorOfLogo;
}
<DataContract()> _
Public Class CompanyLogo
<DataMember()> _
Private ShapeOfLogo As Shape
<DataMember()> _
Private ColorOfLogo As Integer
End Class
在下面的代码中演示了编写 CompanyLogo
类型的正确方法。
[DataContract]
[KnownType(typeof(CircleType))]
[KnownType(typeof(TriangleType))]
public class CompanyLogo2
{
[DataMember]
private Shape ShapeOfLogo;
[DataMember]
private int ColorOfLogo;
}
<DataContract(), KnownType(GetType(CircleType)), KnownType(GetType(TriangleType))> _
Public Class CompanyLogo2
<DataMember()> _
Private ShapeOfLogo As Shape
<DataMember()> _
Private ColorOfLogo As Integer
End Class
只要反序列化外部类型 CompanyLogo2
,反序列化引擎就会了解有关 CircleType
和 TriangleType
,因此能够查找“Circle”和“Triangle”数据协定的匹配类型。
示例 2
在下面的示例中,尽管 CustomerTypeA
和 CustomerTypeB
都具有 Customer
数据协定,但是只要反序列化 CustomerTypeB
就会创建 PurchaseOrder
的实例,因为只有 CustomerTypeB
对反序列化引擎是已知的。
public interface ICustomerInfo
{
string ReturnCustomerName();
}
[DataContract(Name = "Customer")]
public class CustomerTypeA : ICustomerInfo
{
public string ReturnCustomerName()
{
return "no name";
}
}
[DataContract(Name = "Customer")]
public class CustomerTypeB : ICustomerInfo
{
public string ReturnCustomerName()
{
return "no name";
}
}
[DataContract]
[KnownType(typeof(CustomerTypeB))]
public class PurchaseOrder
{
[DataMember]
ICustomerInfo buyer;
[DataMember]
int amount;
}
Public Interface ICustomerInfo
Function ReturnCustomerName() As String
End Interface
<DataContract(Name:="Customer")> _
Public Class CustomerTypeA
Implements ICustomerInfo
Public Function ReturnCustomerName() _
As String Implements ICustomerInfo.ReturnCustomerName
Return "no name"
End Function
End Class
<DataContract(Name:="Customer")> _
Public Class CustomerTypeB
Implements ICustomerInfo
Public Function ReturnCustomerName() _
As String Implements ICustomerInfo.ReturnCustomerName
Return "no name"
End Function
End Class
<DataContract(), KnownType(GetType(CustomerTypeB))> _
Public Class PurchaseOrder
<DataMember()> _
Private buyer As ICustomerInfo
<DataMember()> _
Private amount As Integer
End Class
示例 3
在下面的示例中, Hashtable 将其内容在内部存储为 Object。 若要成功反序列化哈希表,反序列化引擎必须知道那里可能出现的一组可能类型。 在这种情况下,我们预先知道只有 Book
和 Magazine
对象存储在 Catalog
中,因此使用 KnownTypeAttribute 属性添加它们。
[DataContract]
public class Book { }
[DataContract]
public class Magazine { }
[DataContract]
[KnownType(typeof(Book))]
[KnownType(typeof(Magazine))]
public class LibraryCatalog
{
[DataMember]
System.Collections.Hashtable theCatalog;
}
<DataContract()> _
Public Class Book
End Class
<DataContract()> _
Public Class Magazine
End Class
<DataContract(), KnownType(GetType(Book)), KnownType(GetType(Magazine))> _
Public Class LibraryCatalog
<DataMember()> _
Private theCatalog As System.Collections.Hashtable
End Class
示例 4
在下面的示例中,数据协定存储一个数字和要对该数字执行的操作。 Numbers
数据成员可以是整数、整数数组或包含整数的 List<T> 。
注意
仅当使用 SVCUTIL.EXE 来生成 WCF 代理时,此方法才能在客户端使用。 SVCUTIL.EXE 从包含任何已知类型的服务中检索元数据。 如果没有此信息,客户端将不能反序列化该类型。
[DataContract]
[KnownType(typeof(int[]))]
public class MathOperationData
{
private object numberValue;
[DataMember]
public object Numbers
{
get { return numberValue; }
set { numberValue = value; }
}
//[DataMember]
//public Operation Operation;
}
<DataContract(), KnownType(GetType(Integer()))> _
Public Class MathOperationData
Private numberValue As Object
<DataMember()> _
Public Property Numbers() As Object
Get
Return numberValue
End Get
Set(ByVal value As Object)
numberValue = value
End Set
End Property
End Class
这是应用程序代码。
// This is in the service application code:
static void Run()
{
MathOperationData md = new MathOperationData();
// This will serialize and deserialize successfully because primitive
// types like int are always known.
int a = 100;
md.Numbers = a;
// This will serialize and deserialize successfully because the array of
// integers was added to known types.
int[] b = new int[100];
md.Numbers = b;
// This will serialize and deserialize successfully because the generic
// List<int> is equivalent to int[], which was added to known types.
List<int> c = new List<int>();
md.Numbers = c;
// This will serialize but will not deserialize successfully because
// ArrayList is a non-generic collection, which is equivalent to
// an array of type object. To make it succeed, object[]
// must be added to the known types.
ArrayList d = new ArrayList();
md.Numbers = d;
}
' This is in the service application code:
Shared Sub Run()
Dim md As New MathOperationData()
' This will serialize and deserialize successfully because primitive
' types like int are always known.
Dim a As Integer = 100
md.Numbers = a
' This will serialize and deserialize successfully because the array of
' integers was added to known types.
Dim b(99) As Integer
md.Numbers = b
' This will serialize and deserialize successfully because the generic
' List(Of Integer) is equivalent to Integer(), which was added to known types.
Dim c As List(Of Integer) = New List(Of Integer)()
md.Numbers = c
' This will serialize but will not deserialize successfully because
' ArrayList is a non-generic collection, which is equivalent to
' an array of type object. To make it succeed, object[]
' must be added to the known types.
Dim d As New ArrayList()
md.Numbers = d
End Sub
已知类型、继承和接口
使用 KnownTypeAttribute
属性将已知类型与特定类型关联时,已知类型也与该类型的所有派生类型关联。 例如,请参见下面的代码。
[DataContract]
[KnownType(typeof(Square))]
[KnownType(typeof(Circle))]
public class MyDrawing
{
[DataMember]
private object Shape;
[DataMember]
private int Color;
}
[DataContract]
public class DoubleDrawing : MyDrawing
{
[DataMember]
private object additionalShape;
}
<DataContract(), KnownType(GetType(Square)), KnownType(GetType(Circle))> _
Public Class MyDrawing
<DataMember()> _
Private Shape As Object
<DataMember()> _
Private Color As Integer
End Class
<DataContract()> _
Public Class DoubleDrawing
Inherits MyDrawing
<DataMember()> _
Private additionalShape As Object
End Class
DoubleDrawing
类无需 KnownTypeAttribute
属性即可在 Square
字段中使用 Circle
和 AdditionalShape
,因为基类 (Drawing
) 已经应用这些属性。
已知类型只能与类和结构关联,而不能与接口关联。
使用开放式泛型方法的已知类型
可能需要将泛型类型作为已知类型添加。 但是,不能将开放式泛型类型作为参数传递到 KnownTypeAttribute
属性。
通过使用替代机制可以解决此问题:编写一个返回要添加到已知类型集合的类型列表的方法。 然后将方法名称指定为 KnownTypeAttribute
属性的字符串参数(由于某些限制所致)。
方法必须存在于应用 KnownTypeAttribute
属性的类型上,不得接受参数,且必须返回可以分配给 IEnumerable 的 Type的对象。
不能将具有方法名称的 KnownTypeAttribute
属性与实际类型在同一类型上的 KnownTypeAttribute
属性组合在一起。 此外,不能将具有方法名称的多个 KnownTypeAttribute
应用于同一类型。
请参见下面的类。
[DataContract]
public class DrawingRecord<T>
{
[DataMember]
private T theData;
[DataMember]
private GenericDrawing<T> theDrawing;
}
<DataContract()> _
Public Class DrawingRecord(Of T)
<DataMember()> _
Private theData As T
<DataMember()> _
Private theDrawing As GenericDrawing(Of T)
End Class
theDrawing
字段包含泛型类 ColorDrawing
和泛型类 BlackAndWhiteDrawing
的实例,这两个泛型类都是从泛型类 Drawing
继承的。 通常,必须将它们添加到已知类型,但下面不是有效的属性语法。
// Invalid syntax for attributes:
// [KnownType(typeof(ColorDrawing<T>))]
// [KnownType(typeof(BlackAndWhiteDrawing<T>))]
' Invalid syntax for attributes:
' <KnownType(GetType(ColorDrawing(Of T))), _
' KnownType(GetType(BlackAndWhiteDrawing(Of T)))>
因此,必须创建一个方法以返回这些类型。 在下面的代码中演示了编写此类型的正确方法。
[DataContract]
[KnownType("GetKnownType")]
public class DrawingRecord2<T>
{
[DataMember]
private T TheData;
[DataMember]
private GenericDrawing<T> TheDrawing;
private static Type[] GetKnownType()
{
Type[] t = new Type[2];
t[0] = typeof(ColorDrawing<T>);
t[1] = typeof(BlackAndWhiteDrawing<T>);
return t;
}
}
<DataContract(), KnownType("GetKnownType")> _
Public Class DrawingRecord2(Of T)
Private TheData As T
Private TheDrawing As GenericDrawing(Of T)
Private Shared Function GetKnownType() As Type()
Dim t(1) As Type
t(0) = GetType(ColorDrawing(Of T))
t(1) = GetType(BlackAndWhiteDrawing(Of T))
Return t
End Function
End Class
添加已知类型的其他方法
此外,可以通过配置文件添加已知类型。 在不控制需要已知类型才能正确反序列化的类型时,例如将第三方类型库与 Windows Communication Foundation (WCF) 一起使用时,这很有用。
下面的配置文件演示如何在配置文件中指定已知类型。
<configuration>
<system.runtime.serialization>
<dataContractSerializer>
<declaredTypes>
<add type="MyCompany.Library.Shape,
MyAssembly, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=XXXXXX, processorArchitecture=MSIL">
<knownType type="MyCompany.Library.Circle,
MyAssembly, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=XXXXXX, processorArchitecture=MSIL"/>
</add>
</declaredTypes>
</dataContractSerializer>
</system.runtime.serialization>
</configuration>
在前面的配置文件中,名为 MyCompany.Library.Shape
的数据协定类型被声明具有已知类型 MyCompany.Library.Circle
。