Serialização e desserialização

O WCF (Windows Communication Foundation) inclui um novo mecanismo de serialização, o DataContractSerializer. O DataContractSerializer converte entre objetos .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 de tipos com suporte, confira Tipos compatíveis com o Serializador de Contrato de Dados. Para uma introdução aos contratos de dados, confira Como usar contratos de dados.

Ao desserializar XML, o serializador usa as classes XmlReader e XmlWriter. Ele também dá suporte às classes XmlDictionaryReader e XmlDictionaryWriter para permitir que ele produza XML otimizado em alguns casos, como, por exemplo, ao usar o formato de XML binário do WCF.

O WCF também inclui um serializador complementar, o NetDataContractSerializer. NetDataContractSerializer:

  • Não é seguro. Para saber mais, confira o Guia de segurança do BinaryFormatter.
  • É semelhante aos serializadores BinaryFormatter e SoapFormatter porque também emite nomes de tipo do .NET Framework como parte dos dados serializados.
  • Ele é usado quando os mesmos tipos são compartilhados nas terminações de serialização e desserialização.

DataContractSerializer e NetDataContractSerializer derivam de uma classe base comum, a XmlObjectSerializer.

Aviso

O DataContractSerializer serializa cadeias de caracteres que contêm caracteres de controle com um valor hexadecimal abaixo de 20 como as 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

Construir uma instância do DataContractSerializer é uma etapa importante. Após a compilação, você não poderá alterar as configurações.

Especificando o tipo da raiz

O tipo da raiz é o tipo a partir do qual as instâncias são serializadas ou desserializadas. O DataContractSerializer tem várias sobrecargas de construtor, mas, pelo menos, um tipo de raiz deve ser fornecido usando o parâmetro type.

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 cria uma instância DataContractSerializer que pode ser usada somente para serializar ou desserializar as instâncias da classe 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.

Especificando tipos conhecidos

Se o polimorfismo estiver envolvido nos tipos que estão sendo serializados que já não estejam sendo tratados usando o atributo KnownTypeAttribute ou algum outro mecanismo, uma lista de tipos conhecidos possíveis deve ser passada para o construtor do serializador usando o parâmetro knownTypes. Para obter mais informações sobre tipos conhecidos, confira Tipos de Contratos de Dados Conhecidos.

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 tipo LibraryItem. A terceira e a quarta classe (Book e Newspaper) herdam da classe 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

O código a seguir constrói uma instância do serializador usando o parâmetro 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.

Especificando o nome e o namespace de raiz padrão

Normalmente, quando um objeto é serializado, o nome padrão e o namespace do elemento XML mais externo são determinados de acordo com o nome e o namespace do contrato de dados. Os nomes de todos os elementos internos são determinados dos nomes de membro de dados, e seu namespace é o namespace do contrato de dados. O exemplo a seguir define os valores Name e Namespace nos construtores das classes DataContractAttribute e 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

Serializar uma instância da classe Person gera XML semelhante ao seguinte.

<PersonContract xmlns="http://schemas.contoso.com">  
  <AddressMember>  
    <StreetMember>123 Main Street</StreetMember>  
   </AddressMember>  
</PersonContract>  

Entretanto, você pode personalizar o nome e o namespace padrão do elemento raiz passando os valores dos parâmetros rootName e rootNamespace para o construtor DataContractSerializer. Observe que o rootNamespace não afeta o namespace dos elementos contidos que correspondem aos membros de dados. Ele afeta somente o namespace do elemento mais externo.

Esses valores podem ser passados como cadeias de caracteres ou instâncias da classe XmlDictionaryString para permitir a otimização usando o formato XML binário.

Definindo a cota máxima de objetos

Algumas sobrecargas do construtor DataContractSerializer têm um parâmetro maxItemsInObjectGraph. Este parâmetro determina o número máximo de objetos que o serializador serializa ou desserializa em uma única chamada de método ReadObject. (O método sempre lê um objeto raiz, mas esse objeto pode ter outros objetos nos 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 grande representação de memória e, portanto, essa cota apenas pode não ser suficiente para evitar um ataque de negação de serviço. Para obter mais informações, confira Considerações de segurança para dados. Se você precisa aumentar esta cota além do valor padrão, é importante fazer isso no lado do envio (serialização) e do recebimento (desserialização) porque isso se aplica tanto para ler e gravar dados.

Viagens de ida e volta

Uma viagem de ida e volta ocorre quando um objeto é desserializado e serializado novamente em uma operação. Portanto, ele vai de XML para uma instância de objeto e volta novamente para um fluxo XML.

Algumas sobrecargas de construtor do DataContractSerializer têm um parâmetro ignoreExtensionDataObject, que é definido como false por padrão. Nesse 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 anterior, e de volta para a versão mais recente sem perda, contanto que o contrato de dados implemente a interface IExtensibleDataObject. Por exemplo, suponha que a versão 1 do contrato de dados de Person contenha os membros de dados Name e PhoneNumber, e a versão 2 adicione um membro Nickname. Se IExtensibleDataObject estiver implementado, ao enviar informações da versão 2 para a versão 1, os dados de Nickname estarão armazenados e, em seguida, emitidos novamente quando os dados forem serializados novamente; portanto, nenhum dado é perdido na viagem. Para obter mais informações, confira Contratos de dados compatíveis com encaminhamento e Controle de versão dos contratos de dados.

Preocupações de segurança e validade do esquema com viagens de ida e volta

As viagens de ida e volta podem ter implicações de segurança. Por exemplo, desserializar e armazenar grandes quantidades de dados desconhecidos podem ser um risco de segurança. Pode haver problemas de segurança sobre emitir novamente esses dados que não haja nenhuma maneira para verificar, especialmente se assinaturas digitais estiverem envolvidas. Por exemplo, no cenário anterior, o ponto de extremidade da versão 1 pode ser assinar um valor de Nickname que contém dados mal-intencionados. Finalmente, pode haver preocupações de validade de esquema: um ponto de extremidade pode querer sempre emitir os dados que sigam restritamente o contrato indicado e não nenhum valor extra. No exemplo anterior, o contrato do ponto de extremidade da versão 1 diz que emite somente Name e PhoneNumber, e se a validação do esquema estiver sendo usada, emitir o valor extra de Nickname causa falha na validação.

Ativar e desativar viagens de ida e volta

Para desativar viagens de ida e volta, não implemente a interface IExtensibleDataObject. Se você não tiver controle sobre os tipos, defina o parâmetro ignoreExtensionDataObject como true para alcançar o mesmo efeito.

Preservação do gráfico do objeto

Normalmente, o serializador não se importa 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 os campos billTo 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, essa abordagem tem as seguintes características, que podem ser indesejáveis:

  • Desempenho. Replicar dados é ineficiente.

  • Referências circulares. Se os objetos se referirem a si mesmos, mesmo por meio de outros objetos, serializar por replicação resultará em um loop infinito. (O serializador gera um SerializationException se isso acontece.)

  • Semântica. Muitas 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 sobrecargas de construtor do DataContractSerializer têm um parâmetro preserveObjectReferences (o padrão é false). Quando esse parâmetro estiver definido como true, um método especial de referências de objeto de codificação, que somente o WCF entende, é usado. Quando estiver definido como true, o exemplo de código XML agora se parecerá com o 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 dado é serializada apenas uma vez e recebe um número de identificação, e os usos subsequentes resultam em uma referência a dados já serializados.

Importante

Se os atributos “id” e “ref” estiverem presentes no XMLElement do contrato de dados, o atributo “ref” será aceito e o atributo “ID”, ignorado.

É importante entender as limitações desse modo:

  • O XML que o DataContractSerializer produz com preserveObjectReferences definido como true não é interoperável com nenhuma outra tecnologia, e pode ser acessado somente por outra instância DataContractSerializer, também com o preserveObjectReferences definido como true.

  • Não há suporte de metadados (esquema) para esse recurso. O esquema que é gerado é válido somente para o caso em que preserveObjectReferences está definido como false.

  • Esse recurso pode fazer o processo de serialização e desserialização ser executado mais lentamente. Embora os dados não precisem ser replicados, as comparações adicionais de objeto devem ser executadas nesse modo.

Cuidado

Quando o modo preserveObjectReferences está ativado, ele é especialmente importante para definir o valor de maxItemsInObjectGraph para a cota correta. Devido à maneira como as matrizes são tratadas nesse modo, é fácil para que um invasor construir uma pequena mensagem mal-intencionada que resulta em um grande consumo de memória limitada somente pela cota de maxItemsInObjectGraph.

Especificando um substituto para os contratos de dados

Algumas sobrecargas de construtor do DataContractSerializer têm um parâmetro dataContractSurrogate, que pode ser definido como null por padrão. Caso contrário, você pode usá-lo para especificar uma substituição de contrato de dados, que é um tipo que implementa a interface de IDataContractSurrogate. Você pode, em seguida, usar a interface para personalizar o processo de serialização e desserialização. Para saber mais, confira Substitutos do contrato de dados.

Serialização

As informações a seguir aplicam-se a qualquer classe que herda de XmlObjectSerializer, incluindo as classes DataContractSerializer e NetDataContractSerializer.

Serialização simples

A maneira mais básica para serializar um objeto é passá-lo para o método WriteObject. Há três sobrecargas, cada uma para escrever em um Stream, XmlWriter ou XmlDictionaryWriter. Com a sobrecarga Stream, a saída é XML na codificação UTF-8. Com a sobrecarga XmlDictionaryWriter, o serializador otimiza a saída para XML binário.

Ao usar o método WriteObject, o serializador usa o nome e o namespace padrão para o elemento wrapper e grava ele junto com o conteúdo (confira a seção anterior denominada “Especificando o nome e o namespace de raiz padrão”).

O exemplo a seguir demonstra a escrita com um 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)

Isso gera um XML semelhante ao seguinte.

<Person>  
  <Name>Jay Hamlin</Name>  
  <Address>123 Main St.</Address>  
</Person>  

Serialização passo a passo

Use os métodos WriteStartObject, WriteObjectContent e WriteEndObject para gravar o elemento final, escrever o conteúdo do objeto e fechar o elemento wrapper, respectivamente.

Observação

Não há nenhuma sobrecarga de Stream desses métodos.

Essa serialização passo a passo tem dois usos comuns. Um é inserir conteúdo como atributos ou comentários entre WriteStartObject e WriteObjectContent, conforme mostrado no 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 gera um XML semelhante ao seguinte.

<Person serializedBy="myCode">  
  <Name>Jay Hamlin</Name>  
  <Address>123 Main St.</Address>  
</Person>  

Outro uso comum é evitar usar WriteStartObject e WriteEndObject totalmente, e escrever seu próprio elemento wrapper personalizado (ou até mesmo não escrever um wrapper), conforme 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 gera um XML semelhante ao seguinte.

<MyCustomWrapper>  
  <Name>Jay Hamlin</Name>  
  <Address>123 Main St.</Address>  
</MyCustomWrapper>  

Observação

Usar a serialização passo a passo pode resultar em XML de esquema inválido.

Desserialização

As informações a seguir aplicam-se a qualquer classe que herda de XmlObjectSerializer, incluindo as classes DataContractSerializer e NetDataContractSerializer.

A maneira mais básica de desserializar um objeto é chamar uma das sobrecargas de método ReadObject. Há três sobrecargas, cada uma para ler comum XmlDictionaryReader, XmlReader ou Stream. Observe que a sobrecarga de Stream cria um XmlDictionaryReader textual que não é protegido por nenhuma cota, e deve ser usada somente para ler dados confiáveis.

Observe também que o objeto que o método ReadObject retorna deve ser convertido para o tipo apropriado.

O código a seguir constrói uma instância DataContractSerializer e um XmlDictionaryReader e, em seguida, desserializa uma instância 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)

Antes de chamar o método ReadObject, posicione a leitora XML no elemento wrapper ou em um nó de não conteúdo que precede o elemento wrapper. Você pode fazer isso chamando o método Read de XmlReader 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 enviar o leitor para ReadObject.

Ao usar uma das sobrecargas simples de ReadObject, o desserializador procura o nome e o namespace padrão no elemento wrapper (confira a seção anterior “Especificando o nome e o namespace de raiz padrão”) e gera uma exceção se encontrar um elemento desconhecido. No exemplo anterior, o elemento wrapper <Person> é esperado. O método IsStartObject é chamado para verificar se o leitor está localizado em um elemento que seja nomeado como esperado.

Há uma maneira de desabilitar a verificação de nome desse elemento wrapper; algumas sobrecargas do método ReadObject têm o parâmetro booliano verifyObjectName, que é definido como true por padrão. Quando definidas como false, o nome e o namespace do elemento wrapper são ignorados. Isso é útil para ler o XML que foi escrito usando o mecanismo passo a passo de serialização 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 gera nomes de assembly e de tipo completos do .NET Framework no XML serializável. 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, alguns 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 mal-intencionados. Usar o NetDataContractSerializer com dados não confiáveis deverá ser feito apenas se um Associador de Serialização for usado (usando a propriedade Binder ou o parâmetro de construtor). O associador permite que apenas tipos seguros sejam carregados. O mecanismo Associador é idêntico ao usado pelos tipos no System.Runtime.Serialization.

  • Controle de versão. Usar nomes completos de tipo e assembly no XML restringe significativamente o controle de versão de tipos. O exemplo a seguir não pode ser modificado: nomes de tipo, namespaces, nomes de assembly e versões de assembly. Definir a propriedade AssemblyFormat ou o parâmetro do construtor como Simple em vez do valor padrão de Full permite alterações de versão do assembly, mas não para tipos de parâmetros genéricos.

  • Interoperabilidade. Como os nomes de tipo e assembly do .NET Framework estão incluídos no XML, as plataformas que não forem o .NET Framework não podem acessar os dados resultantes.

  • Desempenho. Gravar os nomes de tipo e assembly aumenta significativamente o tamanho do XML resultante.

Esse mecanismo é semelhante à serialização binária ou SOAP usada pelo .NET Framework remoto (especificamente, BinaryFormatter e SoapFormatter).

Usar NetDataContractSerializer é semelhante a usar 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 será desnecessário se os nomes de tipo forem serializados no XML.

  • Os construtores não aceitam um substituto do contrato de dados. Em vez disso, aceitam um parâmetro ISurrogateSelector chamado surrogateSelector (que mapeia para a propriedade SurrogateSelector). Este é um mecanismo substituto herdado.

  • Os construtores aceitam um parâmetro chamado assemblyFormat do FormatterAssemblyStyle que mapeia para a propriedade AssemblyFormat. Conforme discutido anteriormente, isso pode ser usado para melhorar os recursos de controle de versão do serializador. Isso é idêntico ao mecanismo FormatterAssemblyStyle na serialização binária ou SOAP.

  • Os construtores aceitam um parâmetro StreamingContext chamado context que mapeia para a propriedade Context. Você pode usar isso para passar informações para os tipos que estão sendo serializados. Esse uso é idêntico ao do mecanismo StreamingContext usado em outras classes System.Runtime.Serialization.

  • Os métodos Serialize e Deserialize são aliases para os métodos WriteObject 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, confira Serialização binária.

Os formatos XML que o NetDataContractSerializer e o DataContractSerializer usam não são normalmente compatíveis. Ou seja, tentar serializar com um desses serializadores e desserializar com o outro não é um cenário com suporte.

Além disso, observe que o NetDataContractSerializer não gera o nome completo do tipo e do assembly do .NET Framework para cada nó no grafo de objetos. Ele gera essas informações apenas quando são ambíguas. Isto é, ele gera no nível do objeto raiz e para qualquer caso polimórfico.

Confira também