数据协定已知类型

KnownTypeAttribute 类允许您预先指定应该在反序列化期间包括在考虑范围内的类型。 有关工作示例,请参阅 Known Types 示例。

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

  • 已发送的数据协定派生自预期的数据协定。 有关详细信息,请参阅数据协定等效性中有关继承的一节。 在该情况下,传输的数据没有与接收终结点所预期相同的数据协定。

  • 要传输的信息的声明类型是接口,而非类、结构或枚举。 因此,无法预先知道实际发送了实现接口的哪个类型,接收终结点就无法预先确定已传输数据的数据协定。

  • 要传输的信息的声明类型是 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 成员设置为 ShapeOfLogoCircleType 对象,则可以序列化下面的 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 ,反序列化引擎就会了解有关 CircleTypeTriangleType ,因此能够查找“Circle”和“Triangle”数据协定的匹配类型。

示例 2

在下面的示例中,尽管 CustomerTypeACustomerTypeB 都具有 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。 若要成功反序列化哈希表,反序列化引擎必须知道那里可能出现的一组可能类型。 在这种情况下,我们预先知道只有 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 属性即可在 Square 字段中使用 CircleAdditionalShape ,因为基类 (Drawing) 已经应用这些属性。

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

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

可能需要将泛型类型作为已知类型添加。 但是,不能将开放式泛型类型作为参数传递到 KnownTypeAttribute 属性。

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

方法必须存在于应用 KnownTypeAttribute 属性的类型上,不得接受参数,且必须返回可以分配给 IEnumerableType的对象。

不能将具有方法名称的 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

另请参阅