Serialização e desserialização
O Windows Communication Foundation (WCF) inclui um novo mecanismo de serialização, o DataContractSerializer. O DataContractSerializer traduz entre objetos do .NET Framework e XML, em ambas as direções. Este tópico explica como o serializador funciona.
Ao serializar objetos do .NET Framework, o serializador compreende uma variedade de modelos de programação de serialização, incluindo o novo modelo de contrato de dados. Para obter uma lista completa dos tipos suportados, consulte Tipos suportados pelo Data Contract Serializer. Para obter uma introdução aos contratos de dados, consulte Usando contratos de dados.
Ao desserializar XML, o serializador usa as XmlReader classes e XmlWriter . Ele também suporta as XmlDictionaryReader classes e XmlDictionaryWriter para permitir que ele produza XML otimizado em alguns casos, como ao usar o formato XML binário WCF.
O WCF também inclui um serializador complementar, o NetDataContractSerializer. O NetDataContractSerializer:
- Não é seguro. Para obter mais informações, consulte o guia de segurança BinaryFormatter.
- É semelhante aos BinaryFormatter serializadores e SoapFormatter porque também emite nomes de tipo do .NET Framework como parte dos dados serializados.
- É usado quando os mesmos tipos são compartilhados na serialização e a desserialização termina.
Ambos DataContractSerializer e NetDataContractSerializer derivam de uma classe de base comum, XmlObjectSerializer.
Aviso
O DataContractSerializer serializa cadeias de caracteres contendo caracteres de controle com um valor hexadecimal abaixo de 20 como entidades XML. Isso pode causar um problema com um cliente não-WCF ao enviar esses dados para um serviço WCF.
Criando uma instância DataContractSerializer
A construção de uma instância do DataContractSerializer é um passo importante. Após a construção, você não pode alterar nenhuma das configurações.
Especificando o tipo de raiz
O tipo raiz é o tipo do qual as instâncias são serializadas ou desserializadas. O DataContractSerializer tem muitas sobrecargas de construtor, mas, no mínimo, um tipo de raiz deve ser fornecido usando o type
parâmetro.
Um serializador criado para um determinado tipo de raiz não pode ser usado para serializar (ou desserializar) outro tipo, a menos que o tipo seja derivado do tipo raiz. O exemplo a seguir mostra duas classes.
[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
Esse código constrói uma instância do DataContractSerializer
que pode ser usada apenas para serializar ou desserializar instâncias da Person
classe.
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.
Especificando tipos conhecidos
Se o polimorfismo estiver envolvido nos tipos que estão sendo serializados que ainda não são manipulados usando o KnownTypeAttribute atributo ou algum outro mecanismo, uma lista de possíveis tipos conhecidos deve ser passada para o construtor do serializador usando o knownTypes
parâmetro. Para obter mais informações sobre tipos conhecidos, consulte Tipos conhecidos de contrato de dados.
O exemplo a seguir mostra uma classe, LibraryPatron
, que inclui uma coleção de um tipo específico, o LibraryItem
. A segunda classe define o LibraryItem
tipo. A terceira e as quatro classes (Book
e Newspaper
) herdam da LibraryItem
classe.
[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
O código a seguir constrói uma instância do serializador usando o knownTypes
parâmetro.
// 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.
Especificando o nome da raiz padrão e o namespace
Normalmente, quando um objeto é serializado, o nome padrão e o namespace do elemento XML externo são determinados de acordo com o nome do contrato de dados e o namespace. Os nomes de todos os elementos internos são determinados a partir de nomes de membros de dados, e seu namespace é o namespace do contrato de dados. O exemplo a seguir define Name
e Namespace
valores nos construtores das DataContractAttribute classes and 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
A serialização de uma instância da Person
classe produz XML semelhante ao seguinte.
<PersonContract xmlns="http://schemas.contoso.com">
<AddressMember>
<StreetMember>123 Main Street</StreetMember>
</AddressMember>
</PersonContract>
No entanto, você pode personalizar o nome padrão e o namespace do elemento raiz passando os valores dos rootName
parâmetros e rootNamespace
para o DataContractSerializer construtor. Observe que o rootNamespace
não afeta o namespace dos elementos contidos que correspondem aos membros de dados. Ele afeta apenas o namespace do elemento mais externo.
Esses valores podem ser passados como cadeias de caracteres ou instâncias da XmlDictionaryString classe para permitir sua otimização usando o formato XML binário.
Definindo a cota máxima de objetos
Algumas DataContractSerializer
sobrecargas de construtor têm um maxItemsInObjectGraph
parâmetro. Este parâmetro determina o número máximo de objetos que o serializador serializa ou desserializa em uma única ReadObject chamada de método. (O método sempre lê um objeto raiz, mas esse objeto pode ter outros objetos em seus membros de dados. Esses objetos podem ter outros objetos, e assim por diante.) O padrão é 65536. Observe que, ao serializar ou desserializar matrizes, cada entrada de matriz conta como um objeto separado. Além disso, observe que alguns objetos podem ter uma representação de memória grande e, portanto, essa cota por si só pode não ser suficiente para evitar um ataque de negação de serviço. Para obter mais informações, consulte Considerações de segurança para dados. Se você precisar aumentar essa cota além do valor padrão, é importante fazê-lo nos lados de envio (serialização) e recebimento (desserialização), pois isso se aplica a ambos ao ler e gravar dados.
Ações de Ida e Volta
Uma viagem de ida e volta ocorre quando um objeto é desserializado e reserializado em uma operação. Assim, ele vai de XML para uma instância de objeto e novamente para um fluxo XML.
Algumas DataContractSerializer
sobrecargas do construtor têm um ignoreExtensionDataObject
parâmetro, que é definido como false
por padrão. Neste modo padrão, os dados podem ser enviados em uma viagem de ida e volta de uma versão mais recente de um contrato de dados através de uma versão mais antiga e de volta para a versão mais recente sem perda, desde que o contrato de dados implemente a IExtensibleDataObject interface. Por exemplo, suponha que a Person
versão 1 do contrato de dados contenha os membros e PhoneNumber
data e a Name
versão 2 adicione um Nickname
membro. Se IExtensibleDataObject
for implementado, ao enviar informações da versão 2 para a versão 1, os Nickname
dados são armazenados e, em seguida, reemitidos quando os dados são serializados novamente, portanto, nenhum dado é perdido na viagem de ida e volta. Para obter mais informações, consulte Forward-Compatible Data Contracts e Data Contract Versioning.
Preocupações de segurança e validade de esquema com viagens de ida e volta
As viagens de ida e volta podem ter implicações em termos de segurança. Por exemplo, desserializar e armazenar grandes quantidades de dados estranhos pode ser um risco de segurança. Pode haver preocupações de segurança sobre a reemissão desses dados que não há como verificar, especialmente se as assinaturas digitais estiverem envolvidas. Por exemplo, no cenário anterior, o ponto de extremidade da versão 1 poderia estar assinando um Nickname
valor que contém dados mal-intencionados. Finalmente, pode haver preocupações de validade de esquema: um ponto de extremidade pode querer sempre emitir dados que respeitem estritamente seu contrato declarado e não quaisquer valores extras. No exemplo anterior, o contrato do ponto de extremidade da versão 1 diz que ele emite apenas Name
e PhoneNumber
, e se a validação de esquema estiver sendo usada, emitir o valor extra Nickname
faz com que a validação falhe.
Ativando e desativando viagens de ida e volta
Para desativar viagens de ida e volta, não implemente a IExtensibleDataObject interface. Se você não tiver controle sobre os tipos, defina o ignoreExtensionDataObject
parâmetro para true
obter o mesmo efeito.
Preservação de gráficos de objetos
Normalmente, o serializador não se preocupa com a identidade do objeto, como no código a seguir.
[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
O código a seguir cria uma ordem de compra.
// 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
Observe que billTo
os campos e shipTo
são definidos para a mesma instância de objeto. No entanto, o XML gerado duplica as informações duplicadas e é semelhante ao XML a seguir.
<PurchaseOrder>
<billTo><street>123 Main St.</street></billTo>
<shipTo><street>123 Main St.</street></shipTo>
</PurchaseOrder>
No entanto, esta abordagem tem as seguintes características, que podem ser indesejáveis:
Desempenho. A replicação de dados é ineficiente.
Referências circulares. Se os objetos se referem a si mesmos, mesmo através de outros objetos, a serialização por replicação resulta em um loop infinito. (O serializador lança um SerializationException se isso acontecer.)
Semântica. Às vezes, é importante preservar o fato de que duas referências são para o mesmo objeto, e não para dois objetos idênticos.
Por esses motivos, algumas DataContractSerializer
sobrecargas do construtor têm um preserveObjectReferences
parâmetro (o padrão é false
). Quando esse parâmetro é definido como true
, um método especial de codificação de referências de objeto, que somente o WCF entende, é usado. Quando definido como true
, o exemplo de código XML agora é semelhante ao seguinte.
<PurchaseOrder ser:id="1">
<billTo ser:id="2"><street ser:id="3">123 Main St.</street></billTo>
<shipTo ser:ref="2"/>
</PurchaseOrder>
O namespace "ser" refere-se ao namespace de serialização padrão, http://schemas.microsoft.com/2003/10/Serialization/
. Cada parte dos dados é serializada apenas uma vez e recebe um número de ID, e os usos subsequentes resultam em uma referência aos dados já serializados.
Importante
Se ambos os atributos "id" e "ref" estiverem presentes no contrato XMLElement
de dados, o atributo "ref" será honrado e o atributo "id" será ignorado.
É importante compreender as limitações desta modalidade:
O XML que o
DataContractSerializer
produz compreserveObjectReferences
set totrue
não é interoperável com nenhuma outra tecnologia e pode ser acessado apenas por outraDataContractSerializer
instância, também compreserveObjectReferences
set totrue
.Não há suporte a metadados (esquema) para esse recurso. O esquema que é produzido é válido apenas para o caso em
preserveObjectReferences
que é definido comofalse
.Esse recurso pode fazer com que o processo de serialização e desserialização seja executado mais lentamente. Embora os dados não precisem ser replicados, comparações de objetos extras devem ser realizadas nesse modo.
Atenção
Quando o preserveObjectReferences
modo está ativado, é especialmente importante definir o valor para a cota maxItemsInObjectGraph
correta. Devido à forma como as matrizes são tratadas nesse modo, é fácil para um invasor construir uma pequena mensagem maliciosa que resulta em um grande consumo de memória limitado apenas pela cota maxItemsInObjectGraph
.
Especificando um substituto de contrato de dados
Algumas DataContractSerializer
sobrecargas do construtor têm um dataContractSurrogate
parâmetro, que pode ser definido como null
. Caso contrário, você pode usá-lo para especificar um substituto de contrato de dados, que é um tipo que implementa a IDataContractSurrogate interface. Em seguida, você pode usar a interface para personalizar o processo de serialização e desserialização. Para obter mais informações, consulte Substitutos de contrato de dados.
Serialização
As informações a seguir se aplicam a qualquer classe herdada do XmlObjectSerializer, incluindo as DataContractSerializer classes e NetDataContractSerializer .
Serialização simples
A maneira mais básica de serializar um objeto é passá-lo para o WriteObject método. Existem três sobrecargas, uma para escrever para um Stream, um XmlWriter, ou um XmlDictionaryWriter. Com a Stream sobrecarga, a saída é XML na codificação UTF-8. Com a XmlDictionaryWriter sobrecarga, o serializador otimiza sua saída para XML binário.
Ao usar o WriteObject método, o serializador usa o nome padrão e o namespace para o elemento wrapper e o grava junto com o conteúdo (consulte a seção anterior "Especificando o nome da raiz padrão e o namespace").
O exemplo a seguir demonstra a escrita com um XmlDictionaryWriterarquivo .
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)
Isso produz XML semelhante ao seguinte.
<Person>
<Name>Jay Hamlin</Name>
<Address>123 Main St.</Address>
</Person>
Serialização passo a passo
Use os WriteStartObjectmétodos , WriteObjectContente para WriteEndObject escrever o elemento final, gravar o conteúdo do objeto e fechar o elemento wrapper, respectivamente.
Nota
Não há Stream sobrecargas desses métodos.
Essa serialização passo a passo tem dois usos comuns. Uma delas é inserir conteúdos como atributos ou comentários entre WriteStartObject
e WriteObjectContent
, como mostra o exemplo a seguir.
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)
Isso produz XML semelhante ao seguinte.
<Person serializedBy="myCode">
<Name>Jay Hamlin</Name>
<Address>123 Main St.</Address>
</Person>
Outro uso comum é evitar o uso WriteStartObject e WriteEndObject escrever seu próprio elemento wrapper personalizado (ou até mesmo pular a escrita de um wrapper completamente), como mostrado no código a seguir.
xdw.WriteStartElement("MyCustomWrapper");
dcs.WriteObjectContent(xdw, p);
xdw.WriteEndElement();
xdw.WriteStartElement("MyCustomWrapper")
dcs.WriteObjectContent(xdw, p)
xdw.WriteEndElement()
Isso produz XML semelhante ao seguinte.
<MyCustomWrapper>
<Name>Jay Hamlin</Name>
<Address>123 Main St.</Address>
</MyCustomWrapper>
Nota
O uso da serialização passo a passo pode resultar em XML inválido para o esquema.
Desserialização
As informações a seguir se aplicam a qualquer classe herdada do XmlObjectSerializer, incluindo as DataContractSerializer classes e NetDataContractSerializer .
A maneira mais básica de desserializar um objeto é chamar uma das sobrecargas de ReadObject método. Existem três sobrecargas, uma para cada leitura com um XmlDictionaryReader, um XmlReader
, ou um Stream
. Observe que a Stream
sobrecarga cria um texto XmlDictionaryReader que não é protegido por nenhuma cota e deve ser usado apenas para ler dados confiáveis.
Observe também que o objeto que o ReadObject
método retorna deve ser convertido para o tipo apropriado.
O código a seguir constrói uma instância do DataContractSerializer e um XmlDictionaryReadere, em seguida, desserializa uma Person
instância.
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)
Antes de chamar o ReadObject método, posicione o leitor XML no elemento wrapper ou em um nó que não seja de conteúdo que precede o elemento wrapper. Você pode fazer isso chamando o Read método da XmlReader derivação ou sua derivação e testando o NodeType, conforme mostrado no código a seguir.
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
Observe que você pode ler atributos neste elemento wrapper antes de entregar o leitor ao ReadObject
.
Ao usar uma das sobrecargas simples ReadObject
, o desserializador procura o nome padrão e o namespace no elemento wrapper (consulte a seção anterior, "Especificando o nome da raiz padrão e o namespace") e lança uma exceção se encontrar um elemento desconhecido. No exemplo anterior, o <Person>
elemento wrapper é esperado. O IsStartObject método é chamado para verificar se o leitor está posicionado em um elemento que é nomeado como esperado.
Há uma maneira de desativar essa verificação de nome do elemento wrapper; algumas sobrecargas do método tomam o ReadObject
parâmetro verifyObjectName
Boolean , que é definido como true
por padrão. Quando definido como false
, o nome e o namespace do elemento wrapper são ignorados. Isso é útil para ler XML que foi escrito usando o mecanismo de serialização passo a passo descrito anteriormente.
Usando o NetDataContractSerializer
A principal diferença entre o DataContractSerializer
e o NetDataContractSerializer é que o DataContractSerializer
usa nomes de contrato de dados, enquanto o NetDataContractSerializer
output assembly completo do .NET Framework e nomes de tipo no XML serializado. Isso significa que exatamente os mesmos tipos devem ser compartilhados entre os pontos de extremidade de serialização e desserialização. Isso significa que o mecanismo de tipos conhecidos não é necessário com o NetDataContractSerializer
porque os tipos exatos a serem desserializados são sempre conhecidos.
No entanto, vários problemas podem ocorrer:
Segurança. Qualquer tipo encontrado no XML que está sendo desserializado é carregado. Isso pode ser explorado para forçar o carregamento de tipos maliciosos. O uso do
NetDataContractSerializer
com dados não confiáveis deve ser feito somente se um Serialization Binder for usado (usando a propriedade ou o Binder parâmetro do construtor). O aglutinante permite que apenas tipos seguros sejam carregados. O mecanismo Binder é idêntico ao que digita no uso do System.Runtime.Serialization namespace.Controlo de versões. O uso de nomes completos de tipo e assembly no XML restringe severamente como os tipos podem ser versionados. O seguinte não pode ser alterado: nomes de tipo, namespaces, nomes de assembly e versões de assembly. Definir a propriedade ou o AssemblyFormat parâmetro do construtor para Simple em vez do valor padrão de permite alterações na versão do assembly, mas não para tipos de Full parâmetros genéricos.
Interoperabilidade. Como os nomes de tipo e assembly do .NET Framework estão incluídos no XML, plataformas diferentes do .NET Framework não podem acessar os dados resultantes.
Desempenho. Escrever os nomes de tipo e assembly aumenta significativamente o tamanho do XML resultante.
Esse mecanismo é semelhante à serialização binária ou SOAP usada pela comunicação remota do .NET Framework (especificamente, o e o BinaryFormatterSoapFormatter).
Usar o NetDataContractSerializer
é semelhante ao uso do DataContractSerializer
, com as seguintes diferenças:
Os construtores não exigem que você especifique um tipo de raiz. Você pode serializar qualquer tipo com a mesma instância do
NetDataContractSerializer
.Os construtores não aceitam uma lista de tipos conhecidos. O mecanismo de tipos conhecidos é desnecessário se os nomes de tipo são serializados no XML.
Os construtores não aceitam um contrato de substituição de dados. Em vez disso, eles aceitam um ISurrogateSelector parâmetro chamado
surrogateSelector
(que mapeia para a SurrogateSelector propriedade). Trata-se de um mecanismo substituto legado.Os construtores aceitam um parâmetro chamado
assemblyFormat
do FormatterAssemblyStyle que mapeia para a AssemblyFormat propriedade. Como discutido anteriormente, isso pode ser usado para aprimorar os recursos de controle de versão do serializador. Isso é idêntico ao FormatterAssemblyStyle mecanismo na serialização binária ou SOAP.Os construtores aceitam um StreamingContext parâmetro chamado
context
que mapeia para a Context propriedade. Você pode usar isso para passar informações para tipos que estão sendo serializados. Esta utilização é idêntica à StreamingContext do mecanismo utilizado noutras System.Runtime.Serialization classes.Os Serialize métodos e Deserialize são aliases para os WriteObject métodos e ReadObject . Eles existem para fornecer um modelo de programação mais consistente com serialização binária ou SOAP.
Para obter mais informações sobre esses recursos, consulte Serialização binária.
Os formatos XML que o NetDataContractSerializer
e o DataContractSerializer
uso normalmente não são compatíveis. Ou seja, tentar serializar com um desses serializadores e desserializar com o outro não é um cenário suportado.
Além disso, observe que o NetDataContractSerializer
não produz o tipo completo do .NET Framework e o nome do assembly para cada nó no gráfico de objeto. Só produz essa informação quando é ambígua. Ou seja, ele produz no nível do objeto raiz e para quaisquer casos polimórficos.