데이터 계약 알려진 형식

KnownTypeAttribute 클래스를 사용하면 고려 사항에 포함해야 하는 형식을 deserialization을 수행하는 동안 미리 지정할 수 있습니다. 작업 예제는 Known Types 예제를 참조하십시오.

일반적으로 클라이언트와 서비스 간에 매개 변수와 반환 값을 전달할 때 두 엔드포인트는 전송할 데이터의 모든 데이터 계약을 공유합니다. 그러나 다음과 같은 경우는 그렇지 않습니다.

  • 보낸 데이터 계약은 필요한 데이터 계약에서 파생됩니다. 자세한 내용은 데이터 계약 동등성 상속에 대한 섹션을 참조하세요. 이 경우 전송한 데이터에는 수신하는 엔드포인트에서 필요한 것과 동일한 데이터 계약이 없습니다.

  • 전송할 정보에 대해 선언된 형식은 클래스, 구조체 또는 열거형이 아니라 인터페이스입니다. 따라서 인터페이스를 구현하는 형식을 실제로 보냈는지 미리 알 수 없으므로 수신하는 엔드포인트에서 전송된 데이터에 대한 데이터 계약을 미리 확인할 수 없습니다.

  • 전송할 정보에 대해 선언된 형식은 Object입니다. 모든 형식은 Object에서 상속되며 실제로 보낸 형식을 미리 알 수 없으므로 수신하는 엔드포인트에서 전송된 데이터에 대한 데이터 계약을 미리 확인할 수 없습니다. 이것은 첫 번째 항목의 특별한 경우입니다. 모든 데이터 계약은 Object에 대해 생성되는 빈 데이터 계약인 기본값에서 파생됩니다.

  • .NET Framework 형식을 비롯한 일부 형식에는 앞의 세 범주 중 하나에 있는 멤버가 포함됩니다. 예를 들어 HashtableObject 를 사용하여 해시 테이블에 실제 개체를 저장합니다. 이러한 형식을 serialize할 때 받는 쪽에서 이러한 멤버에 대한 데이터 계약을 미리 확인할 수 없습니다.

KnownTypeAttribute 클래스

데이터가 수신하는 엔드포인트에 도착하면 WCF 런타임에서는 CLR(공용 언어 런타임) 형식의 인스턴스로 데이터를 역직렬화하려고 시도합니다. deserialization을 위해 인스턴스화되는 형식은 메시지 내용이 따르는 데이터 계약을 확인하도록 들어오는 메시지를 먼저 검사하여 선택됩니다. 그런 다음 deserialization 엔진은 메시지 내용과 호환되는 데이터 계약을 구현하는 CLR 형식을 찾습니다. 이 프로세스 중에 역직렬화 엔진에서 허용하는 후보 형식 집합을 역직렬 변환기의 "알려진 형식" 집합이라고 합니다.

deserialization 엔진에 형식을 알리는 한 가지 방법은 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 멤버가 ShapeOfLogo 또는 CircleType 개체로 설정된 경우 역직렬화 엔진이 데이터 계약 이름 "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 프록시를 생성하는 경우 클라이언트 쪽에서만 작동합니다. 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 필드에서 KnownTypeAttributeSquare 을 사용할 Circle 특성이 기본 클래스( AdditionalShape )에 이미 적용되었기 때문에Drawing클래스에는 이러한 특성이 필요하지 않습니다.

알려진 형식은 인터페이스가 아닌 클래스와 구조체에만 연결할 수 있습니다.

개방형 제네릭 메서드를 사용한 알려진 형식

제네릭 형식을 알려진 형식으로 추가해야 할 수 있습니다. 하지만 개방형 제네릭 형식은 KnownTypeAttribute 특성에 대한 매개 변수로 전달될 수 없습니다.

이 문제는 대체 메커니즘을 사용하여 해결할 수 있습니다. 알려진 형식 컬렉션에 추가할 형식 목록을 반환하는 메서드를 작성합니다. 그런 다음 몇 가지 제한 사항으로 인해 이 메서드 이름이 KnownTypeAttribute 특성에 대한 문자열 인수로 지정됩니다.

이 메서드는 KnownTypeAttribute 특성이 적용되는 형식에 있어야 하고, static 메서드여야 하며, 매개 변수를 취하지 않아야 하고, 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

알려진 형식을 추가하는 다른 방법

또한 구성 파일을 통해 알려진 형식을 추가할 수 있습니다. 이 기능은 WCF(Windows Communication Foundation)에 타사 형식 라이브러리를 사용할 때처럼 적합한 deserialization에 알려진 형식이 필요한 형식을 제어하지 않을 때 유용합니다.

다음 구성 파일에서는 구성 파일에 알려진 형식을 지정하는 방법을 보여 줍니다.

<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 을 알려진 형식으로 포함하도록 선언되었습니다.

참고 항목