数据协定已知类型

通过 KnownTypeAttribute 类,可以提前指定应在反序列化期间考虑的类型。 有关工作示例,请参阅 “已知类型” 示例。

通常,在客户端和服务之间传递参数和返回值时,两个终结点共享要传输的数据的所有数据协定。 但是,在以下情况下,情况并非如此:

  • 已发送的数据协定派生自预期的数据协定。 有关详细信息,请参阅 有关数据协定等效中的继承部分。 在这种情况下,传输的数据与接收终结点的预期数据协定不同。

  • 要传输的信息的声明类型是一个接口,而不是类、结构或枚举。 因此,事先无法知道实际被发送的接口实现类型,接收终结点也就无法事先确定传输数据的数据契约。

  • 要传输的信息的声明类型为 Object。 由于每种类型都继承自 Object,并且无法提前知道实际发送哪种类型,因此接收终结点无法提前确定传输的数据的数据协定。 这是第一项的特殊情况:每个数据协定派生自默认数据协定,这是为该 Object协定生成的空白数据协定。

  • 某些类型(包括 .NET Framework 类型)具有上述三个类别之一的成员。 例如, Hashtable 用于 Object 将实际对象存储在哈希表中。 序列化这些类型时,接收方无法提前确定这些成员的数据协定。

KnownTypeAttribute 类

当数据到达接收终结点时,WCF 运行时会尝试将数据反序列化为公共语言运行时 (CLR) 类型的实例。 通过首先检查传入消息选择为反序列化而实例化的类型,以确定消息内容遵循的数据协定。 然后,反序列化引擎会尝试查找实现与消息内容兼容的数据协定的 CLR 类型。 反序列化引擎在此过程中允许的候选类型集称为反序列化程序的一组“已知类型”。

让反序列化引擎了解类型的方法之一是使用 KnownTypeAttribute。 该属性不能应用于单个数据成员,只能应用于整个数据协定类型。 该特性应用于可以是类或结构 的外部类型 。 在最基本的用法中,应用特性将类型指定为“已知类型”。这会导致已知类型成为已知类型的集合的一部分,只要外部类型或通过其成员引用的任何对象被反序列化。 可以将多个属性应用于同一 KnownTypeAttribute 类型。

已知类型和原始类型

基元类型,以及被视为基元的某些类型(例如,DateTimeXmlElement)始终是“已知”的,永远不必通过此机制添加。 但是,必须显式添加基元类型的数组。 大多数集合都被视为等效于数组。 (非泛型集合被视为与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类,但如果成员设置为某个CircleTypeTriangleType对象,则无法反序列化ShapeOfLogo,因为反序列化引擎无法识别数据协定名称为“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 时,反序列化引擎就会知道 CircleTypeTriangleType 因此,能够找到“圆形”和“三角形”数据协定的匹配类型。

示例 2

在以下示例中,即使CustomerTypeACustomerTypeB都具有Customer数据协定,但每当PurchaseOrder被反序列化时,都会创建一个CustomerTypeB实例,因为反序列化引擎只知道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. 若要成功反序列化哈希表,反序列化引擎必须知道可能发生在那里的可能类型集。 在这种情况下,我们事先知道,只有BookMagazine对象存储在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属性就可以在AdditionalShape字段中使用SquareCircle,因为基类(Drawing)已经应用了这些属性。

已知类型只能与类和结构相关联,而不能与接口相关联。

使用开放泛型方法的已知类型

可能需要将泛型类型添加为已知类型。 但是,无法将开放的泛型类型作为参数传递给 KnownTypeAttribute 特性。

可以使用替代机制解决此问题:编写返回要添加到已知类型集合的类型列表的方法。 然后,由于某些限制,方法名称被指定为KnownTypeAttribute属性的字符串参数。

该方法必须存在于应用KnownTypeAttribute属性的类型上,必须是静态的,必须不接受参数,并且必须返回一个可以分配给Type中的IEnumerable的对象。

不能在同一类型上将 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

另请参阅