資料合約已知型別

KnownTypeAttribute 類別可讓您預先指定在還原序列化期間應該納入考量的型別。 如需實用範例,請參閱 Known Types 範例。

一般來說,當您在用戶端與服務之間傳送參數並傳回值時,兩邊的端點都會共用要傳送之資料的所有資料合約。 但是,這種現象在下列情況中不會出現:

  • 傳送的資料合約是由預期的資料合約衍生而來。 如需詳細資訊,請參閱資料合約等價中有關於繼承的小節。 在此情況下,所傳送資料的資料合約與接收的端點所預期的資料合約不會一樣。

  • 要傳送的資訊宣告型別是一種介面,而不是類別、結構或列舉。 因此,您無法預先得知實際傳送了哪種可實作介面的型別,也因此接收的端點無法預先判斷已傳送資料的資料合約。

  • 要傳送的資訊宣告型別為 Object。 由於每種型別都繼承自 Object,而且您無法預先得知實際傳送的型別,因此接收的端點無法預先判斷已傳送資料的資料合約。 以下是第一個項目的特殊情況:每個資料合約都衍生自預設、且針對 Object產生的空白資料合約。

  • 有些型別 (包括 .NET Framework 型別) 的成員隸屬於先前三大類別的其中一個類別。 例如, Hashtable 會透過 Object 將實際物件儲存到雜湊資料表中。 在序列化這些型別時,接收的一方無法預先判斷這些成員的資料合約。

KnownTypeAttribute 類別

當資料抵達接收的端點時,WCF 執行階段會嘗試將資料還原序列化為 Common Language Runtime (CLR) 型別的執行個體。 還原系列化作業所產生的型別,首先會經由檢查傳入訊息來判斷訊息內容所符合的資料合約來加以選定。 接著,還原序列化引擎會嘗試尋找可實作資料合約 (相容於訊息內容) 的 CLR 型別。 在此處理序中,我們會將還原序列化引擎所允許的候選型別集合稱為還原序列化程式的「已知型別」集合。

讓還原序列化引擎識別型別的一種方式,就是使用 KnownTypeAttribute。 屬性無法套用至個別資料成員,只能套用至整個資料合約型別。 屬性會套用至可以是類別或結構的「 外部型別 」(Outer Type)。 在屬性最基本的用途當中,套用屬性會將型別指定為「已知型別」。這樣一來,每當外部型別的物件或是透過其成員參照的任何物件進行還原序列化,已知型別就會變成已知型別集合的一部分。 超過一個以上的 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 物件,由於還原序列化引擎無法識別任何具有資料合約名稱 "Circle" 或 "Triangle" 的型別,您將可以序列化下列 TriangleType 類別,但是無法加以還原序列化。

[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 Proxy,這將只能在用戶端上運作。 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 做為已知的型別。

另請參閱