Сериализация и десериализация
Windows Communication Foundation (WCF) включает новый модуль сериализации,DataContractSerializer Преобразуется DataContractSerializer между объектами платформа .NET Framework и XML в обоих направлениях. В данном разделе объясняется, как работает сериализатор.
При сериализации платформа .NET Framework объектов сериализатор понимает различные модели программирования сериализации, включая новую модель контракта данных. Полный список поддерживаемых типов см. в разделе Types Supported by the Data Contract Serializer. Вводные сведения о контрактах данных см. в разделе Using Data Contracts.
При десериализации XML-кода сериализатор использует классы XmlReader и XmlWriter . Он также поддерживает XmlDictionaryReader и XmlDictionaryWriter классы, чтобы он мог создавать оптимизированный XML в некоторых случаях, например при использовании двоичного xml-формата WCF.
WCF также включает в себя сериализатор компаньона , .NetDataContractSerializer NetDataContractSerializer:
- Не является безопасным. Дополнительные сведения см. в статье Руководство по безопасности BinaryFormatter.
- Аналогичен сериализаторам, SoapFormatter так как BinaryFormatter он также выдает имена типов платформа .NET Framework в составе сериализованных данных.
- Используется, когда одни и те же типы используются для сериализации и десериализации.
Оба DataContractSerializer класса и NetDataContractSerializer производные от общего базового класса XmlObjectSerializer.
Предупреждение
Класс DataContractSerializer сериализует строки, содержащие управляющие символы с шестнадцатеричным значением меньше 20 в виде сущностей XML. Это может привести к проблеме с клиентом, отличным от WCF, при отправке таких данных в службу WCF.
Создание экземпляра DataContractSerializer
Создание экземпляра DataContractSerializer является важным этапом. После создания экземпляра невозможно менять какие-либо настройки.
Задание корневого типа
Корневой тип - это тип, экземпляры которого сериализуются и десериализуются. Сериализатор DataContractSerializer имеет множество перегрузок конструктора, однако хотя бы корневой тип должен предоставляться с использованием параметра type
.
Сериализатор, созданный для определенного корневого типа, невозможно использовать для сериализации (или десериализации) другого типа, если только он не наследуется от корневого типа. В следующем примере показаны два класса.
[DataContract]
public class Person
{
// Code not shown.
}
[DataContract]
public class PurchaseOrder
{
// Code not shown.
}
<DataContract()> _
Public Class Person
' Code not shown.
End Class
<DataContract()> _
Public Class PurchaseOrder
' Code not shown.
End Class
Этот код создает экземпляр DataContractSerializer
, который можно использовать только для сериализации и десериализации экземпляров класса Person
.
DataContractSerializer dcs = new DataContractSerializer(typeof(Person));
// This can now be used to serialize/deserialize Person but not PurchaseOrder.
Dim dcs As New DataContractSerializer(GetType(Person))
' This can now be used to serialize/deserialize Person but not PurchaseOrder.
Задание известных типов
Если в сериализуемых типах, которые еще не были обработаны с помощью атрибута KnownTypeAttribute или какого-либо другого механизма, применяется полиморфизм, то конструктору сериализатора с помощью параметра knownTypes
необходимо передать список возможных известных типов. Дополнительные сведения о известных типах см. в разделе "Известные типы контракта данных".
В следующем примере продемонстрирован класс LibraryPatron
, который включает коллекцию указанного типа LibraryItem
. Второй класс определяет тип LibraryItem
. Третий и четвертый классы (Book
и Newspaper
) наследуются от класса LibraryItem
.
[DataContract]
public class LibraryPatron
{
[DataMember]
public LibraryItem[] borrowedItems;
}
[DataContract]
public class LibraryItem
{
// Code not shown.
}
[DataContract]
public class Book : LibraryItem
{
// Code not shown.
}
[DataContract]
public class Newspaper : LibraryItem
{
// Code not shown.
}
<DataContract()> _
Public Class LibraryPatron
<DataMember()> _
Public borrowedItems() As LibraryItem
End Class
<DataContract()> _
Public Class LibraryItem
' Code not shown.
End Class
<DataContract()> _
Public Class Book
Inherits LibraryItem
' Code not shown.
End Class
<DataContract()> _
Public Class Newspaper
Inherits LibraryItem
' Code not shown.
End Class
В следующем примере кода демонстрируется создание экземпляра сериализатора с использованием параметра knownTypes
.
// Create a serializer for the inherited types using the knownType parameter.
Type[] knownTypes = new Type[] { typeof(Book), typeof(Newspaper) };
DataContractSerializer dcs =
new DataContractSerializer(typeof(LibraryPatron), knownTypes);
// All types are known after construction.
' Create a serializer for the inherited types using the knownType parameter.
Dim knownTypes() As Type = {GetType(Book), GetType(Newspaper)}
Dim dcs As New DataContractSerializer(GetType(LibraryPatron), knownTypes)
' All types are known after construction.
Задание корневого имени и пространства имен по умолчанию
Как правило, при сериализации объекта имя и пространство имен по умолчанию внешнего элемента XML определяются в соответствии с именем и пространством имен контракта данных. Имена всех внутренних элементов определяются на основе имен членов данных, а их пространство имен представляет собой пространство имен контракта данных. В следующем примере задаются значения имени Name
и пространства имен Namespace
в конструкторах классов DataContractAttribute и DataMemberAttribute .
[DataContract(Name = "PersonContract", Namespace = "http://schemas.contoso.com")]
public class Person2
{
[DataMember(Name = "AddressMember")]
public Address theAddress;
}
[DataContract(Name = "AddressContract", Namespace = "http://schemas.contoso.com")]
public class Address
{
[DataMember(Name = "StreetMember")]
public string street;
}
<DataContract(Name:="PersonContract", [Namespace]:="http://schemas.contoso.com")> _
Public Class Person2
<DataMember(Name:="AddressMember")> _
Public theAddress As Address
End Class
<DataContract(Name:="AddressContract", [Namespace]:="http://schemas.contoso.com")> _
Public Class Address
<DataMember(Name:="StreetMember")> _
Public street As String
End Class
В ходе сериализации экземпляра класса Person
создается XML-код, подобный следующему.
<PersonContract xmlns="http://schemas.contoso.com">
<AddressMember>
<StreetMember>123 Main Street</StreetMember>
</AddressMember>
</PersonContract>
Однако можно настроить имя и пространство имен корневого элемента по умолчанию, передав значения параметров rootName
и rootNamespace
конструктору DataContractSerializer . Обратите внимание, что пространство имен rootNamespace
не влияет на пространство имен содержащихся в нем элементов, которые соответствуют членам данных. Оно влияет только на пространство имен внешнего элемента.
Эти значения могут быть переданы в виде строк или экземпляров класса XmlDictionaryString , что позволяет их оптимизировать с использованием двоичного XML-формата.
Задание максимальной квоты объекта
Некоторые перегрузки конструктора DataContractSerializer
имеют параметр maxItemsInObjectGraph
. Этот параметр определяет максимальное число объектов, которые сериализатор сериализует или десериализует за один вызов метода ReadObject . (Метод всегда считывает один корневой объект, но этот объект может иметь другие объекты в его членах данных. Эти объекты могут иметь другие объекты и т. д.) Значение по умолчанию — 65536. Обратите внимание, что при сериализации или десериализации массивов каждая запись массива считается отдельным объектом. Также обратите внимание, что некоторые объекты могут иметь большое представление в памяти, поэтому одной этой квоты может быть недостаточно для предотвращения атак типа "отказ в обслуживании". Дополнительные сведения см. в разделе "Вопросы безопасности для данных". При необходимости увеличить квоту по сравнению со значением по умолчанию следует увеличить ее как на стороне отправки (сериализации), так и на стороне получения (десериализации), так как квота действует как при считывании, так и при записи данных.
Круговые пути
Цикл обработки совершается, когда объект десериализуется и повторно сериализуется за одну операцию. Таким образом, он перемещается от XML-кода к экземпляру объекта, а затем возвращается в поток XML.
Некоторые перегрузки конструктора DataContractSerializer
имеют параметр ignoreExtensionDataObject
, для которого по умолчанию задано значение false
. В этом режиме по умолчанию данные можно без потерь отправлять в цикл обработки от новой версии контракта данных через старую версию обратно к новой при условии, что контракт данных реализует интерфейс IExtensibleDataObject . Предположим, например, что версия 1 контракта данных Person
содержит члены данных Name
и PhoneNumber
, а версия 2 добавляет член Nickname
. Если реализуется объект IExtensibleDataObject
, то при отправке информации из версии 2 в версию 1 данные Nickname
сохраняются, а затем снова выдаются при повторной сериализации данных, поэтому данные не теряются при прохождении цикла обработки. Дополнительные сведения см. в статье "Переадресация контрактов данных " и "Управление версиями контракта данных".
Проблемы безопасности и допустимости схемы в циклах обработки
Циклы обработки влияют на безопасность. Например, десериализация и сохранение больших объемов лишних данных могут представлять угрозу безопасности. Проблемы безопасности при повторной выдаче этих данных обусловлены невозможностью провести проверку, особенно если в процессе задействованы цифровые сигнатуры. Например, в вышеприведенном сценарии конечная точка версии 1 могла бы подписать значение Nickname
, которое содержит вредоносные данные. Наконец, могут возникнуть проблемы с допустимостью схемы: конечная точка может всегда выдавать только данные, которые строго соответствуют ее заявленному контракту, и не поддерживать других значений. В предыдущем примере в контракте конечной точки версии 1 говорится, что точка выдает только данные Name
и PhoneNumber
, а при использовании проверки допустимости схемы выдача дополнительного значения Nickname
приводит к сбою проверки.
Включение и отключение циклов обработки
Для отключения циклов обработки не реализуйте интерфейс IExtensibleDataObject . Если невозможно управлять типами, для достижения такого же эффекта необходимо задать параметру ignoreExtensionDataObject
значение true
.
Сохранение графа объекта
Как правило, удостоверение объекта не имеет значения для сериализатора, как показано в следующем примере кода.
[DataContract]
public class PurchaseOrder
{
[DataMember]
public Address billTo;
[DataMember]
public Address shipTo;
}
[DataContract]
public class Address
{
[DataMember]
public string street;
}
<DataContract()> _
Public Class PurchaseOrder
<DataMember()> _
Public billTo As Address
<DataMember()> _
Public shipTo As Address
End Class
<DataContract()> _
Public Class Address
<DataMember()> _
Public street As String
End Class
В следующем примере кода демонстрируется создание заказа на покупку.
// Construct a purchase order:
Address adr = new Address();
adr.street = "123 Main St.";
PurchaseOrder po = new PurchaseOrder();
po.billTo = adr;
po.shipTo = adr;
' Construct a purchase order:
Dim adr As New Address()
adr.street = "123 Main St."
Dim po As New PurchaseOrder()
po.billTo = adr
po.shipTo = adr
Обратите внимание, что полям billTo
и shipTo
задан один и тот же экземпляр объекта. Однако созданный XML-код дублирует повторяющуюся информацию и по внешнему виду аналогичен следующему XML-коду.
<PurchaseOrder>
<billTo><street>123 Main St.</street></billTo>
<shipTo><street>123 Main St.</street></shipTo>
</PurchaseOrder>
Этот подход имеет следующие характеристики, которые могут оказаться нежелательными.
Производительность. Репликация данных неэффективна.
Циклические ссылки. Если объекты ссылаются на самих себя (даже через другие объекты), сериализация результатов репликации приводит к бесконечному циклу. (В этом случае сериализатор создает исключение SerializationException .)
Семантика. Иногда очень важно сохранить отнесение двух ссылок к одному и тому же объекту, а не к двум идентичным объектам.
По этим причинам некоторые перегрузки конструктора DataContractSerializer
имеют параметр preserveObjectReferences
(по умолчанию ему задано значение false
). Если для этого параметра задано true
значение, используется специальный метод ссылок на объекты кодирования, которые используются только в WCF. Если задано значение true
, пример XML-кода выглядит следующим образом.
<PurchaseOrder ser:id="1">
<billTo ser:id="2"><street ser:id="3">123 Main St.</street></billTo>
<shipTo ser:ref="2"/>
</PurchaseOrder>
Пространство имен ser относится к стандартному пространству имен сериализации. http://schemas.microsoft.com/2003/10/Serialization/
Каждый блок данных сериализуется только один раз, ему присваивается идентификационный номер, и при использовании этих блоков в будущем создается ссылка на уже сериализованные данные.
Внимание
Если в контракте данных XMLElement
присутствует и атрибут "id", и атрибут "ref", используется атрибут "ref", а атрибут "id" игнорируется.
Важно понимать существующие в этом режиме ограничения.
XML-код, создаваемый сериализатором
DataContractSerializer
с помощьюpreserveObjectReferences
с заданным значениемtrue
, не поддерживает возможность взаимодействия с какими-либо другими технологиями, доступ к нему может осуществлять только другой экземплярDataContractSerializer
, дляpreserveObjectReferences
которого также задано значениеtrue
.Эта функция не обеспечивается поддержкой метаданных (схемы). Созданная схема действительна, только если
preserveObjectReferences
задано значениеfalse
.Эта функция может замедлить процесс сериализации и десериализации. Несмотря на отсутствие необходимости в репликации данных, в этом режиме необходимо выполнять дополнительные сравнения объектов.
Внимание
Если включен режим preserveObjectReferences
, очень важно задать правильную квоту значению maxItemsInObjectGraph
. Особенности обработки массивов в этом режиме позволяют злоумышленнику легко создавать небольшое вредоносное сообщение, которое приводит к расходованию больших объемов памяти, и препятствовать этому можно только заданием квоты maxItemsInObjectGraph
.
Задание суррогата контракта данных
Некоторые перегрузки конструктора DataContractSerializer
имеют параметр dataContractSurrogate
, для которого может быть задано значение null
. В противном случае можно использовать этот параметр для задания суррогата контракта данных, представляющего собой тип, который реализует интерфейс IDataContractSurrogate . Затем с помощью интерфейса можно настроить процесс сериализации и десериализации. Дополнительные сведения см. в статье "Суррогаты контракта данных".
Сериализация
Следующая информация применима к любому классу, наследуемому от сериализатора XmlObjectSerializer, включая классы DataContractSerializer и NetDataContractSerializer
Простая сериализация
Основным способом сериализации объекта является его передача методу WriteObject . Существует три перегрузки, каждая из которых предназначена для записи в Stream, XmlWriterили XmlDictionaryWriterсоответственно. При использовании перегрузки Stream на выходе получается XML-код в кодировке UTF-8. При использовании перегрузки XmlDictionaryWriter сериализатор оптимизирует объекты на выходе для двоичного XML-формата.
При использовании WriteObject метода сериализатор использует имя и пространство имен по умолчанию для элемента оболочки и записывает его вместе с содержимым (см. предыдущий раздел "Указание корневого имени и пространства имен по умолчанию").
Следующий пример демонстрирует запись с помощью XmlDictionaryWriter.
Person p = new Person();
DataContractSerializer dcs =
new DataContractSerializer(typeof(Person));
XmlDictionaryWriter xdw =
XmlDictionaryWriter.CreateTextWriter(someStream,Encoding.UTF8 );
dcs.WriteObject(xdw, p);
Dim p As New Person()
Dim dcs As New DataContractSerializer(GetType(Person))
Dim xdw As XmlDictionaryWriter = _
XmlDictionaryWriter.CreateTextWriter(someStream, Encoding.UTF8)
dcs.WriteObject(xdw, p)
Это позволяет создать XML-код, подобный следующему.
<Person>
<Name>Jay Hamlin</Name>
<Address>123 Main St.</Address>
</Person>
Пошаговая сериализация
Для записи конечного элемента и содержимого объекта и закрытия программы-оболочки используйте методы WriteStartObject, WriteObjectContentи WriteEndObject соответственно.
Примечание.
Перегрузок этих методов Stream нет.
Описанная здесь пошаговая сериализация, как правило, используется в двух случаях. Во-первых, чтобы вставлять содержимое, такое как атрибуты или комментарии, между WriteStartObject
и WriteObjectContent
, как показано в следующем примере.
dcs.WriteStartObject(xdw, p);
xdw.WriteAttributeString("serializedBy", "myCode");
dcs.WriteObjectContent(xdw, p);
dcs.WriteEndObject(xdw);
dcs.WriteStartObject(xdw, p)
xdw.WriteAttributeString("serializedBy", "myCode")
dcs.WriteObjectContent(xdw, p)
dcs.WriteEndObject(xdw)
Это позволяет создать XML-код, подобный следующему.
<Person serializedBy="myCode">
<Name>Jay Hamlin</Name>
<Address>123 Main St.</Address>
</Person>
Во-вторых, пошаговая сериализация часто применяется, чтобы полностью избежать использования WriteStartObject и WriteEndObject и создать собственную пользовательскую программу-оболочку (или пропустить создание программы-оболочки в принципе), как показано в следующем примере кода.
xdw.WriteStartElement("MyCustomWrapper");
dcs.WriteObjectContent(xdw, p);
xdw.WriteEndElement();
xdw.WriteStartElement("MyCustomWrapper")
dcs.WriteObjectContent(xdw, p)
xdw.WriteEndElement()
Это позволяет создать XML-код, подобный следующему.
<MyCustomWrapper>
<Name>Jay Hamlin</Name>
<Address>123 Main St.</Address>
</MyCustomWrapper>
Примечание.
Использование пошаговой сериализации может привести к созданию XML-кода с недействительной схемой.
Десериализация
Следующая информация применима к любому классу, наследуемому от сериализатора XmlObjectSerializer, включая классы DataContractSerializer и NetDataContractSerializer
Основным способом десериализации объекта является вызов одной из перегрузок метода ReadObject . Существует три перегрузки, каждая из которых предназначена для считывания с помощью XmlDictionaryReader, XmlReader
или Stream
соответственно. Обратите внимание, что перегрузка Stream
создает текстовое средство чтения XmlDictionaryReader , не защищенное никакими квотами, следовательно, его нужно использовать только для чтения надежных данных.
Также обратите внимание, что объект, возвращаемый методом ReadObject
, должен быть приведен к соответствующему типу.
В следующем примере кода демонстрируется создание экземпляра сериализатора DataContractSerializer и средства чтения XmlDictionaryReader, а также последующая десериализация экземпляра Person
.
DataContractSerializer dcs = new DataContractSerializer(typeof(Person));
FileStream fs = new FileStream(path, FileMode.Open);
XmlDictionaryReader reader =
XmlDictionaryReader.CreateTextReader(fs, new XmlDictionaryReaderQuotas());
Person p = (Person)dcs.ReadObject(reader);
Dim dcs As New DataContractSerializer(GetType(Person))
Dim fs As New FileStream(path, FileMode.Open)
Dim reader As XmlDictionaryReader = _
XmlDictionaryReader.CreateTextReader(fs, New XmlDictionaryReaderQuotas())
Dim p As Person = CType(dcs.ReadObject(reader), Person)
Прежде чем вызывать метод ReadObject , необходимо разместить средство чтения XML в программе-оболочке или не имеющем содержимого узле, который предшествует программе-оболочке. Это можно осуществить вызовом метода Read устройства чтения XmlReader или производного метода и тестированием типа NodeType, как показано в следующем примере кода.
DataContractSerializer ser = new DataContractSerializer(typeof(Person),
"Customer", @"http://www.contoso.com");
FileStream fs = new FileStream(path, FileMode.Open);
XmlDictionaryReader reader =
XmlDictionaryReader.CreateTextReader(fs, new XmlDictionaryReaderQuotas());
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
if (ser.IsStartObject(reader))
{
Console.WriteLine("Found the element");
Person p = (Person)ser.ReadObject(reader);
Console.WriteLine("{0} {1} id:{2}",
p.Name , p.Address);
}
Console.WriteLine(reader.Name);
break;
}
}
Dim ser As New DataContractSerializer(GetType(Person), "Customer", "http://www.contoso.com")
Dim fs As New FileStream(path, FileMode.Open)
Dim reader As XmlDictionaryReader = XmlDictionaryReader.CreateTextReader(fs, New XmlDictionaryReaderQuotas())
While reader.Read()
Select Case reader.NodeType
Case XmlNodeType.Element
If ser.IsStartObject(reader) Then
Console.WriteLine("Found the element")
Dim p As Person = CType(ser.ReadObject(reader), Person)
Console.WriteLine("{0} {1}", _
p.Name, p.Address)
End If
Console.WriteLine(reader.Name)
End Select
End While
Обратите внимание, что до передачи устройства чтения методу ReadObject
в этой программе-оболочке можно прочитать атрибуты.
При использовании одной из простых ReadObject
перегрузок десериализатор ищет имя и пространство имен по умолчанию в элементе оболочки (см. предыдущий раздел "Указание корневого имени по умолчанию и пространства имен") и создает исключение, если он находит неизвестный элемент. Предполагается, что в предыдущем примере используется программа-оболочка <Person>
. Метод IsStartObject вызывается, чтобы убедиться, что средство чтения размещено в элементе, именованном как ожидается.
Можно отключить такую проверку имени программы-оболочки; некоторые перегрузки метода ReadObject
используют логический параметр verifyObjectName
, которому по умолчанию задано значение true
. Если параметру задано значение false
, имя и пространство имен программы-оболочки игнорируются. Эта функция полезна при чтении XML-кода, который был создан с использованием механизма пошаговой сериализации, описанной ранее.
Использование NetDataContractSerializer
Основное различие между и NetDataContractSerializer темDataContractSerializer
, что DataContractSerializer
использует имена контрактов данных, в то время как NetDataContractSerializer
выходные данные полные платформа .NET Framework сборки и имен типов в сериализованном XML. Это означает, что одни и те же типы должны совместно использоваться конечными точками сериализации и десериализации. Так как при использовании сериализатора NetDataContractSerializer
всегда известны точные типы, которые должны быть десериализованы, механизм известных типов не требуется.
Однако в связи с этим могут возникнуть некоторые проблемы:
Безопасность. Загружается любой тип, обнаруживаемый в десериализуемом XML-коде. Этим можно воспользоваться для принудительной загрузки вредоносных типов. Использование сериализатора
NetDataContractSerializer
с ненадежными данными возможно только при условии применения связывателя сериализации (с использованием свойства Binder или параметра конструктора). Связыватель позволяет загружать только надежные типы. Механизм связывателя аналогичен используемому типами в пространстве имен System.Runtime.Serialization .Управление версиями. Использование полных имен типов и сборок в XML-коде строго ограничивает возможности управления версиями типов. При этом невозможно изменить имена типов, пространства имен, имена и версии сборок. Задание свойству AssemblyFormat или параметру конструктора значения Simple вместо значения по умолчанию Full позволяет изменить версию сборки, но не универсальные типы параметров.
Возможность взаимодействия. Так как имена типов и сборок платформа .NET Framework включены в XML, платформы, отличные от платформа .NET Framework, не могут получить доступ к результирующей данных.
Производительность. Запись имен типов и сборок значительно увеличивает размер получаемого XML-кода.
Этот механизм похож на двоичную или сериализацию SOAP, используемую платформа .NET Framework удаленного взаимодействия (в частности, BinaryFormatter и ).SoapFormatter
Сериализаторы NetDataContractSerializer
и DataContractSerializer
используются практически аналогично со следующими отличиями.
Конструкторы не требуют указания корневого типа. Любой тип можно сериализовать с помощью одного и того же экземпляра сериализатора
NetDataContractSerializer
.Конструкторы не принимают список известных типов. Если имена типов сериализуются в XML-код, механизм известных типов не требуется.
Конструкторы не принимают суррогаты контракта данных. Вместо этого они принимают параметр ISurrogateSelector , называемый
surrogateSelector
(который сопоставляется со свойством SurrogateSelector ). Это устаревший суррогатный механизм.Конструкторы принимают параметр стиля
assemblyFormat
, называемый FormatterAssemblyStyle , который сопоставляется со свойством AssemblyFormat . Как уже отмечалось ранее, эту особенность можно использовать для расширения возможностей сериализатора по управлению версиями. Этот подход аналогичен механизму FormatterAssemblyStyle в двоичной сериализации или сериализации SOAP.Конструкторы принимают параметр StreamingContext , называемый
context
, который сопоставляется со свойством Context . Эту особенность можно использовать для передачи информации в сериализуемые типы. Данный подход аналогичен использованию механизма StreamingContext в других классах System.Runtime.Serialization .Методы Serialize и Deserialize представляют собой псевдонимы для методов WriteObject и ReadObject . Они предоставляют более последовательную модель программирования с двоичной сериализацией или сериализацией SOAP.
Дополнительные сведения об этих функциях см. в разделе "Двоичная сериализация".
Форматы XML, используемые сериализаторами NetDataContractSerializer
и DataContractSerializer
, как правило, не совместимы. Следовательно, возможность сериализации с помощью одного из них и десериализации с помощью другого не поддерживается.
Кроме того, обратите внимание, что не NetDataContractSerializer
выводит полный платформа .NET Framework тип и имя сборки для каждого узла в графе объектов. Он выводит эту информацию, только если она неоднозначна. Таким образом, вывод данных осуществляется на уровне корневого объекта и для любых полиморфных случаев.