Поделиться через


Известные типы контрактов данных

Класс KnownTypeAttribute позволяет заранее задавать типы, которые следует рассматривать при десериализации. Рабочий пример см. в разделе Известные типы.

Обычно при передаче параметров и возвращаемых значений между клиентом и службой обе конечные точки совместно используют все контракты данных, относящиеся к передаваемым данным. Однако в следующих ситуациях это не так:

  • контракт отправляемых данных наследует контракту ожидаемых данных. Дополнительные сведения см. в разделе в подразделе, посвященном наследованию, в разделе Эквивалентность контрактов данных). В этом случае контракт передаваемых данных отличается от контракта данных, ожидаемого принимающей конечной точкой;

  • объявленный тип передаваемых данных является интерфейсом, а не классом, структурой или перечислением. Поэтому невозможно заранее знать, какой именно из реализуемых этим интерфейсом типов будет передан, а следовательно принимающая конечная точка не может заранее определить контракт передаваемых данных;

  • объявлен тип передаваемых данных Object. Поскольку каждый тип наследуется от класса Object, невозможно заранее знать, какой именно тип будет передан, а следовательно принимающая конечная точка не может заранее определить контракт передаваемых данных. Это частный случай первого варианта: каждый контракт данных наследует от контракта данных по умолчанию (пустого контракта), который создается для класса Object;

  • у некоторых типов, включающих типы .NET Framework, имеются члены, относящиеся к одной из указанных выше трех категорий. Например, класс Hashtable использует класс Object для хранения фактических объектов в хэш-таблице. При сериализации таких типов принимающая сторона не может заранее определить контракт данных таких членов.

Класс KnownTypeAttribute

Когда данные прибывают в принимающую конечную точку, среда выполнения WCF предпринимает попытку десериализовать данные в экземпляр типа среды CLR. Тип, экземпляр которого создается в результате десериализации, выбирается в первую очередь по результатам проверки входящего сообщения с целью определения контракта данных, которому соответствует содержимое сообщения. После этого система десериализации пытается найти тип среды CLR, который реализует контракт данных, совместимый с содержимым сообщения. Набор потенциальных типов, которые система десериализации считает допустимыми в результате выполнения этих операций, называется набором "известных типов" десериализатора.

Один из способов уведомления десериализатора о типе — использовать класс KnownTypeAttribute. Этот атрибут нельзя применять к отдельным членам, но следует применять только к целым типам контрактов данных. Атрибут применяется к внешнему типу, который может быть классом или структурой. На самом базовом уровне применение этого атрибута помечает тип в качестве "известного типа". В результате известный тип становится частью набора известных типов всякий раз, когда происходит десериализация объекта внешнего типа или любого объекта, являющегося членом известного типа. К одному и тому же типу можно применить несколько атрибутов KnownTypeAttribute.

Известные типы и примитивы

Типы-примитивы, а также некоторые типы, с которыми обращаются как с примитивами (например, DateTime и XmlElement), всегда являются "известными", и их никогда не нужно добавлять с помощью описанного механизма. Однако массивы типов-примитивов нужно добавлять в явном виде. Большинство коллекций считаются эквивалентами массивов. (Неуниверсальные коллекции считаются эквивалентами массивов Object.) Пример использования примитивов, массивов примитивов и коллекций примитивов см. в примере 4.

ms730167.note(ru-ru,VS.100).gifПримечание
В отличие от других типов-примитивов структура DateTimeOffset по умолчанию не является известным типом, поэтому ее необходимо вручную добавлять в список известных типов.

Примеры

Ниже приведены примеры использования класса KnownTypeAttribute.

Пример 1

Имеется три класса, связанных отношениями наследования.

<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
[DataContract]
public class Shape { }

[DataContract(Name = "Circle")]
public class CircleType : Shape { }

[DataContract(Name = "Triangle")]
public class TriangleType : Shape { }

Показанный ниже класс CompanyLogo можно сериализовать; однако он не может быть сериализован, если член ShapeOfLogo имеет значение CircleType или TriangleType, поскольку система десериализации не распознает типы с именами контрактов данных "Circle" и "Triangle".

<DataContract()> _
Public Class CompanyLogo
    <DataMember()> _
    Private ShapeOfLogo As Shape
    <DataMember()> _
    Private ColorOfLogo As Integer
End Class
[DataContract]
public class CompanyLogo
{
    [DataMember]
    private Shape ShapeOfLogo;
    [DataMember]
    private int ColorOfLogo;
}

В следующем примере кода показано, как правильно описать тип CompanyLogo.

<DataContract(), KnownType(GetType(CircleType)), KnownType(GetType(TriangleType))> _
Public Class CompanyLogo2
    <DataMember()> _
    Private ShapeOfLogo As Shape
    <DataMember()> _
    Private ColorOfLogo As Integer
End Class
[DataContract]
[KnownType(typeof(CircleType))]
[KnownType(typeof(TriangleType))]
public class CompanyLogo2
{
    [DataMember]
    private Shape ShapeOfLogo;
    [DataMember]
    private int ColorOfLogo;

}

Когда происходит десериализация внешнего типа CompanyLogo2, система десериализации знает о типах CircleType и TriangleType и поэтому может найти контракты данных, соответствующие типам "Circle" и "Triangle".

Пример 2

В следующем примере, даже если оба типа CustomerTypeA и CustomerTypeB имеют контракт данных Customer, при десериализации типа PurchaseOrder будет создаваться экземпляр CustomerTypeB, поскольку системе десериализации известен только тип CustomerTypeB.

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

Пример 3

В следующем примере тип Hashtable хранит содержимое в виде Object. Для десериализации хэш-таблицы системе десериализации должен быть известен набор возможных типов, которые представляются этим типом. В данном случае нам известно, что в объекте Catalog хранятся только объекты Book и Magazine, поэтому они добавляются с помощью атрибута KnownTypeAttribute.

<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
[DataContract]
public class Book { }

[DataContract]
public class Magazine { }

[DataContract]
[KnownType(typeof(Book))]
[KnownType(typeof(Magazine))]
public class LibraryCatalog
{
    [DataMember]
    System.Collections.Hashtable theCatalog;
}

Пример 4

В следующем примере контракт данных хранит число и операцию, которую следует выполнить над этим числом. Элемент данных Numbers может быть целым числом, массивом целых чисел или объектом List, содержащим целые числа.

ms730167.Caution(ru-ru,VS.100).gifВнимание!
Работает только на стороне клиента, если SVCUTIL.EXE используется для формирования учетной записи-посредника WCF. SVCUTIL.EXE извлекает метаданные из службы, включая любые известные типы. Без этой информации клиент не сможет десериализовать типы.

<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
[DataContract]
[KnownType(typeof(int[]))]
public class MathOperationData
{
    private object numberValue;
    [DataMember]
    public object Numbers
    {
        get { return numberValue; }
        set { numberValue = value; }
    }
    //[DataMember]
    //public Operation Operation;
}

Ниже показан код приложения.

' 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
// 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;
}

Известные типы, наследование и интерфейсы

Когда известный тип связывается с конкретным типом с помощью атрибута KnownTypeAttribute, он также связывается со всеми производными типами этого типа. См., например, следующий код.

<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
[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;
}

Класс DoubleDrawing не требует, чтобы атрибут KnownTypeAttribute использовал типы Square и Circle в поле AdditionalShape, поскольку эти атрибуты уже применены в базовом классе (Drawing).

Известные типы можно связывать только с классами и структурами, но не с интерфейсами.

Известные типы и открытые универсальные методы

Может возникнуть необходимость добавить в качестве известного типа универсальный тип. Однако открытый универсальный тип невозможно передать в атрибут KnownTypeAttribute в качестве параметра.

Эту проблему можно решить с помощью альтернативного механизма: напишите метод, возвращающий типы, которые следует добавить в коллекцию известных типов. После этого имя метода задается в качестве строкового аргумента атрибута KnownTypeAttribute с некоторыми ограничениями.

Этот метод должен существовать в типе, к которому применяется атрибут KnownTypeAttribute, должен быть статическим, не должен принимать параметров и должен возвращать объект, который можно присвоить интерфейсу IEnumerable типа Type.

Нельзя объединять атрибут KnownTypeAttribute с именем метода и атрибутами KnownTypeAttribute с реальными типами в рамках одного типа. Более того, нельзя применять более одного атрибута KnownTypeAttribute с именем метода к одному и тому же типу.

См. приведенный ниже класс.

<DataContract()> _
Public Class DrawingRecord(Of T)
    <DataMember()> _
    Private theData As T
    <DataMember()> _
    Private theDrawing As GenericDrawing(Of T)
End Class
[DataContract]
public class DrawingRecord<T>
{
    [DataMember]
    private T theData;
    [DataMember]
    private GenericDrawing<T> theDrawing;
}

Поле 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(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
[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;
    }
}

Дополнительные способы добавления известных типов

Типы также можно добавлять в коллекцию ReadOnlyCollection, доступ к которой осуществляется через свойство KnownTypes интерфейса DataContractSerializer.

Кроме того, известные типы можно добавлять с помощью файла конфигурации. Это бывает удобно, если нет возможности управлять типом, требующим для правильной сериализации применения списка известных типов, например при использовании в 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.

См. также

Задачи

Известные типы

Справочник

KnownTypeAttribute
Hashtable
Object
DataContractSerializer
KnownTypes

Основные понятия

Эквивалентность контрактов данных
Создание контрактов служб