Znane typy kontraktów danych

Klasa KnownTypeAttribute umożliwia określenie z wyprzedzeniem typów, które należy uwzględnić podczas deserializacji. Aby zapoznać się z przykładem roboczym, zobacz przykład Znane typy .

Zwykle podczas przekazywania parametrów i zwracania wartości między klientem a usługą oba punkty końcowe współdzielą wszystkie kontrakty danych, które mają być przesyłane. Nie ma to jednak zastosowania w następujących okolicznościach:

  • Wysłany kontrakt danych pochodzi z oczekiwanego kontraktu danych. Aby uzyskać więcej informacji, zobacz sekcję dotyczącą dziedziczenia w artykule Równoważność kontraktu danych. W takim przypadku przesyłane dane nie mają takiego samego kontraktu danych, jak oczekiwano przez odbierający punkt końcowy.

  • Zadeklarowany typ przekazywanych informacji jest interfejsem, w przeciwieństwie do klasy, struktury lub wyliczenia. W związku z tym nie można znać z wyprzedzeniem, który typ implementujący interfejs jest rzeczywiście wysyłany, a zatem punkt końcowy odbierający nie może wcześniej określić kontraktu danych dla przesyłanych danych.

  • Zadeklarowany typ informacji, które mają być przesyłane, to Object. Ponieważ każdy typ dziedziczy z Objectelementu i nie może być wcześniej znany, który typ jest rzeczywiście wysyłany, punkt końcowy odbierający nie może określić z wyprzedzeniem kontraktu danych dla przesyłanych danych. Jest to szczególny przypadek pierwszego elementu: każdy kontrakt danych pochodzi od domyślnego, pustego kontraktu danych generowanego dla elementu Object.

  • Niektóre typy, które obejmują typy programu .NET Framework, mają elementy członkowskie należące do jednej z poprzednich trzech kategorii. Na przykład Hashtable używa Object metody do przechowywania rzeczywistych obiektów w tabeli skrótów. Podczas serializacji tych typów strona odbierania nie może określić z wyprzedzeniem kontraktu danych dla tych elementów członkowskich.

Klasa KnownTypeAttribute

Gdy dane docierają do odbierającego punktu końcowego, środowisko uruchomieniowe programu WCF próbuje deserializować dane w wystąpieniu typu środowiska uruchomieniowego języka wspólnego (CLR). Typ, który jest tworzone na potrzeby deserializacji, jest wybierany przez najpierw sprawdzenie komunikatu przychodzącego w celu określenia kontraktu danych, z którym jest zgodna zawartość komunikatu. Aparat deserializacji próbuje znaleźć typ CLR, który implementuje kontrakt danych zgodny z zawartością komunikatu. Zestaw typów kandydatów, które aparat deserializacji umożliwia w trakcie tego procesu, jest określany jako zestaw deserializatora "znanych typów".

Jednym ze sposobów, aby poinformować aparat deserializacji o typie, jest użycie klasy KnownTypeAttribute. Atrybutu nie można zastosować do poszczególnych składowych danych, tylko do całych typów kontraktów danych. Atrybut jest stosowany do typu zewnętrznego, który może być klasą lub strukturą. W najbardziej podstawowym użyciu zastosowanie atrybutu określa typ jako "znany typ". Powoduje to, że znany typ jest częścią zestawu znanych typów, gdy obiekt typu zewnętrznego lub dowolny obiekt, do którego odwołuje się jego składowe, jest deserializowany. Do tego samego typu można zastosować więcej niż jeden KnownTypeAttribute atrybut.

Znane typy i typy pierwotne

Typy pierwotne, a także niektóre typy traktowane jako typy pierwotne (na przykład DateTime i XmlElement) są zawsze "znane" i nigdy nie muszą być dodawane za pomocą tego mechanizmu. Należy jednak jawnie dodać tablice typów pierwotnych. Większość kolekcji jest uważana za równoważną tablicom. (Kolekcje inne niż ogólne są uważane za równoważne tablicom Object). Przykład użycia elementów pierwotnych, tablic pierwotnych i kolekcji pierwotnych można znaleźć w przykładzie 4.

Uwaga

W przeciwieństwie do innych typów pierwotnych DateTimeOffset struktura nie jest domyślnie znanym typem, dlatego należy ją ręcznie dodać do listy znanych typów.

Przykłady

W poniższych przykładach pokazano klasę używaną KnownTypeAttribute .

Przykład 1

Istnieją trzy klasy z relacją dziedziczenia.

[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

Poniższa CompanyLogo klasa może być serializowana, ale nie można wykonać deserializacji, jeśli ShapeOfLogo element członkowski jest ustawiony na CircleType obiekt lub TriangleType obiekt, ponieważ aparat deserializacji nie rozpoznaje żadnych typów z nazwami kontraktów danych "Circle" lub "Trójkąt".

[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

Prawidłowy sposób na napisanie CompanyLogo typu jest wyświetlany w poniższym kodzie.

[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

Za każdym razem, gdy typ CompanyLogo2 zewnętrzny jest deserializowany, aparat deserializacji wie o CircleType i TriangleType , w związku z tym, jest w stanie znaleźć pasujące typy kontraktów danych "Circle" i "Trójkąt".

Przykład 2

W poniższym przykładzie, mimo że zarówno CustomerTypeA kontrakt danych, jak i CustomerTypeB mają Customer kontrakt danych, wystąpienie CustomerTypeB jest tworzone za każdym razem, gdy PurchaseOrder element jest deserializowany, ponieważ jest znany tylko CustomerTypeB aparatowi deserializacji.

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

Przykład 3

W poniższym przykładzie zawartość Hashtable jest przechowywana wewnętrznie jako Object. Aby pomyślnie wykonać deserializacji tabeli skrótów, aparat deserializacji musi znać zestaw możliwych typów, które mogą tam wystąpić. W tym przypadku wiemy z wyprzedzeniem, że tylko obiekty Book i Magazine są przechowywane w Catalogobiekcie , więc są one dodawane przy użyciu atrybutu 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

Przykład 4

W poniższym przykładzie kontrakt danych przechowuje liczbę i operację do wykonania na liczbie. Element Numbers członkowski danych może być liczbą całkowitą, tablicą liczb całkowitych lub zawierającą List<T> liczby całkowite.

Uwaga

Będzie to działać tylko po stronie klienta, jeśli SVCUTIL.EXE jest używany do generowania serwera proxy WCF. SVCUTIL.EXE pobiera metadane z usługi, w tym wszelkie znane typy. Bez tych informacji klient nie będzie mógł wykonać deserializacji typów.

[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

Jest to kod aplikacji.

// 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

Znane typy, dziedziczenie i interfejsy

Gdy znany typ jest skojarzony z określonym typem przy użyciu atrybutu KnownTypeAttribute , znany typ jest również skojarzony ze wszystkimi typami pochodnymi tego typu. Zobacz na przykład następujący kod.

[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

Klasa DoubleDrawing nie wymaga użycia atrybutu KnownTypeAttributeSquare i Circle w AdditionalShape polu, ponieważ klasa podstawowa (Drawing) ma już zastosowane te atrybuty.

Znane typy mogą być skojarzone tylko z klasami i strukturami, a nie z interfejsami.

Znane typy korzystające z otwartych metod ogólnych

Może być konieczne dodanie typu ogólnego jako znanego typu. Nie można jednak przekazać otwartego typu ogólnego jako parametru do atrybutu KnownTypeAttribute .

Ten problem można rozwiązać przy użyciu alternatywnego mechanizmu: Napisz metodę zwracającą listę typów, które mają zostać dodane do znanej kolekcji typów. Nazwa metody jest następnie określana jako argument ciągu atrybutu KnownTypeAttribute z powodu pewnych ograniczeń.

Metoda musi istnieć w typie, do którego KnownTypeAttribute zastosowano atrybut, musi być statyczna, nie musi przyjmować żadnych parametrów i musi zwrócić obiekt, który można przypisać do TypeIEnumerable klasy .

Nie można połączyć atrybutu KnownTypeAttribute z nazwą metody i KnownTypeAttribute atrybutami z rzeczywistymi typami w tym samym typie. Ponadto nie można zastosować więcej niż jednego KnownTypeAttribute z nazwą metody do tego samego typu.

Zobacz następującą klasę.

[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

Pole theDrawing zawiera wystąpienia klasy ColorDrawing ogólnej i klasy BlackAndWhiteDrawingogólnej , z których oba dziedziczą z klasy Drawingogólnej . Zwykle oba muszą być dodawane do znanych typów, ale poniższa składnia nie jest prawidłowa dla atrybutów.

// 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)))>  

W związku z tym należy utworzyć metodę, aby zwrócić te typy. Prawidłowy sposób na napisanie tego typu, a następnie, jest wyświetlany w poniższym kodzie.

[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

Dodatkowe sposoby dodawania znanych typów

Ponadto znane typy można dodawać za pośrednictwem pliku konfiguracji. Jest to przydatne, gdy nie kontrolujesz typu, który wymaga znanych typów do właściwej deserializacji, na przykład w przypadku używania bibliotek typów innych firm z programem Windows Communication Foundation (WCF).

Poniższy plik konfiguracji przedstawia sposób określania znanego typu w pliku konfiguracji.

<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>

W poprzednim pliku konfiguracji typ kontraktu danych o nazwie MyCompany.Library.Shape jest deklarowany MyCompany.Library.Circle jako znany typ.

Zobacz też