Serialización y deserialización
Windows Communication Foundation (WCF) incluye un nuevo motor de serialización, el DataContractSerializer. DataContractSerializer traduce entre objetos .NET Framework y XML, en ambas direcciones. En este tema se explica cómo funciona el serializador.
Al serializar objetos .NET Framework, el serializador entiende diversos modelos de programación de la serialización, incluido el nuevo modelo de contrato de datos. Para obtener una lista completa de los tipos admitidos, vea Tipos admitidos por el serializador de contrato de datos. Para obtener una introducción a los contratos de datos, vea Utilización de contratos de datos.
Al deserializar XML, el serializador utiliza las clases XmlReader y XmlWriter. También admite las clases XmlDictionaryReader y XmlDictionaryWriter para permitirle generar XML optimizado en algunos casos, como al utilizar el formato XML binario de WCF.
WCF también incluye un serializador complementario, el NetDataContractSerializer. El NetDataContractSerializer es similar a los serializadores SoapFormatter y BinaryFormatter porque también emite los nombres de tipos de .NET Framework como parte de los datos serializados. Se utiliza cuando se comparten los mismos tipos en los extremos de serialización y deserialización. DataContractSerializer y NetDataContractSerializer derivan de una clase base común, XmlObjectSerializer.
Precaución: |
---|
DataContractSerializer serializa cadenas que contienen caracteres de control con un valor hexadecimal inferior a 20 como entidades XML. Esto puede producir problemas si un cliente no WCF envía esos datos a un servicio WCF. |
Creación de una instancia de DataContractSerializer
Construir una instancia de DataContractSerializer es un paso importante. Después de la construcción no puede cambiar ninguno de los valores.
Especificación del tipo de raíz
El tipo de raíz es el tipo en el que las instancias se serializan o deserializan. DataContractSerializer tiene muchas sobrecarga del constructor, pero, como mínimo, se debe proporcionar un tipo de raíz utilizando el parámetro type .
Un serializador creado para un tipo de raíz determinado no se puede utilizar para serializar (o deserializar) otro tipo, a menos que el tipo se derive del tipo de raíz. En el siguiente ejemplo se muestran dos clases.
<DataContract()> _
Public Class Person
' Code not shown.
End Class
<DataContract()> _
Public Class PurchaseOrder
' Code not shown.
End Class
[DataContract]
public class Person
{
// Code not shown.
}
[DataContract]
public class PurchaseOrder
{
// Code not shown.
}
Este código construye una instancia del DataContractSerializer que solo se puede utilizar para serializar o deserializar instancias de la clase Person
.
Dim dcs As New DataContractSerializer(GetType(Person))
'This can now be used to serialize/deserialize Person but not PurchaseOrder.
DataContractSerializer dcs = new DataContractSerializer(typeof(Person));
//This can now be used to serialize/deserialize Person but not PurchaseOrder.
Especificación de los tipos conocidos
Si el polimorfismo está implicado en los tipos que se están serializando que aún no se administran utilizando el atributo KnownTypeAttribute o algún otro mecanismo, se ha de pasar una lista de posibles tipos conocidos al constructor del serializador utilizando el parámetro knownTypes. Para obtener más información sobre los tipos conocidos, vea Tipos conocidos de contratos de datos.
El siguiente ejemplo muestra una clase, LibraryPatron
, que incluye una colección de un tipo específico, LibraryItem
. La segunda clase define el tipo LibraryItem
. Las clases tercera y cuarta Book
y Newspaper
heredan de la clase LibraryItem
.
<DataContract()> _
Public Class LibraryPatron
<DataMember()> _
Public borrowedItems() As LibraryItem
End Class
<DataContract()> _
Public Class LibraryItem
'code not shown
End Class 'LibraryItem
<DataContract()> _
Public Class Book
Inherits LibraryItem
'code not shown
End Class
<DataContract()> _
Public Class Newspaper
Inherits LibraryItem
'code not shown
End Class
El siguiente código construye una instancia del serializador utilizando el parámetro knownTypes.
'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.
//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.
Especificación del espacio de nombres y el nombre de raíz predeterminados
Normalmente, cuando se serializa un objeto, el nombre y espacio de nombres predeterminados del elemento XML extremo se determinan según el nombre y el espacio de nombres del contrato de datos. Los nombres de todos los elementos internos se determinan a partir de los nombres de los miembros de datos y su espacio de nombres es el espacio de nombres del contrato de datos. El siguiente ejemplo establece los valores de Name
y Namespace
en los constructores de las clases DataContractAttribute y DataMemberAttribute.
<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
[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;
}
Al serializar una instancia de la clase Person
, se genera código XML similar al siguiente.
<PersonContract xmlns="http://schemas.contoso.com">
<AddressMember>
<StreetMember>123 Main Street</StreetMember>
</AddressMember>
</PersonContract>
Sin embargo, puede personalizar el nombre y espacio de nombres predeterminado del elemento raíz pasando los valores de los parámetros rootNamespace y rootName al constructor DataContractSerializer. Observe que el rootNamespace no afecta al espacio de nombres de los elementos contenidos que corresponden a miembros de datos. Solo afecta al espacio de nombres del elemento extremo.
Estos valores se pueden pasar como cadenas o instancias de la clase XmlDictionaryString para permitir su optimización mediante el formato XML binario.
Establecimiento de la cuota de objetos máximos
Algunas sobrecargas del constructor de DataContractSerializer tienen un parámetro maxItemsInObjectGraph. Este parámetro determina el número máximo de objetos que el serializador serializa o deserializa en una única llamada al método ReadObject. (El método siempre lee un objeto raíz, pero este objeto puede tener otros objetos en sus miembros de datos. Esos objetos pueden tener otros objetos, etc.) El valor predeterminado es 65536. Tenga en cuenta que al serializar o deserializar las matrices, cada entrada de matriz cuenta como un objeto independiente. Observe también que algunos objetos pueden tener una representación de memoria grande, por lo que esta cuota por sí sola puede no ser suficiente para evitar ataques por denegación de servicio. Para obtener más información, vea Consideraciones de seguridad para datos. Si necesita aumentar esta cuota por encima del valor predeterminado, es importante hacerlo en los lados de envío (serialización) y recepción (deserialización), porque se aplica a ambos al leer y escribir datos.
Acciones de ida y vuelta
Una acción de ida y vuelta (round trip) se produce cuando un objeto se deserializa y se vuelve a serializar en una operación. De este modo, va de XML a una instancia de objeto y de vuelta a una secuencia XML.
Algunas sobrecargas del constructor del DataContractSerializer tienen un parámetro ignoreExtensionDataObject , que está establecido de forma predeterminada en false . En este modo predeterminado, los datos se pueden enviar en un viaje de ida y vuelta (round trip) desde una versión más reciente de un contrato de datos a través de una versión anterior y de vuelta a la versión más reciente sin pérdidas, siempre que el contrato de datos implemente la interfaz IExtensibleDataObject. Por ejemplo, suponga que la versión 1 del contrato de datos de la Person
contiene los miembros de datos PhoneNumber
y Name
, y la versión 2 agrega un miembro Nickname
. Si se implementa IExtensibleDataObject, al enviar información de la versión 2 a la versión 1, los datos Nickname
se almacenan y, a continuación, se vuelven a emitir cuando se vuelven a serializar los datos; por tanto, no se pierden datos en el viaje de ida y vuelta. Para obtener más información, vea Contratos de datos compatibles con el reenvío y Versiones de contratos de datos.
Aspectos a tener en cuenta sobre seguridad y validez del esquema en relación con los viajes de ida y vuelta (round trip)
Los viajes de ida y vuelta pueden tener implicaciones en cuanto a la seguridad. Por ejemplo, deserializar y almacenar grandes cantidades de datos extraños puede constituir un riesgo para la seguridad. Puede haber problemas de seguridad al reemitir estos datos que no se pueden comprobar, sobre todo si hay firmas digitales implicadas. Por ejemplo, en el escenario anterior, el extremo de versión 1 podría estar firmando un valor Nickname
que contuviese datos malintencionados. Por último, puede haber problemas de validez del esquema: un extremo puede desear emitir siempre datos que cumplan de manera estricta el contrato indicado y no valores adicionales. En el ejemplo anterior, el contrato del extremo de versión 1 dice que solo emite Name
y PhoneNumber
, y si se utiliza la validación de esquema, se produciría un error de validación al emitir el valor Nickname
adicional.
Habilitar y deshabilitar los viajes de ida y vuelta
Para desactivar los viajes de ida y vuelta, no implemente la interfaz IExtensibleDataObject. Si no tiene ningún control sobre los tipos, establezca el parámetro ignoreExtensionDataObject en true para lograr el mismo efecto.
Preservación de gráfico de objeto
Normalmente, el serializador no se preocupa de la identidad del objeto, como en el código siguiente.
<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
[DataContract]
public class PurchaseOrder
{
[DataMember]
public Address billTo;
[DataMember]
public Address shipTo;
}
[DataContract]
public class Address
{
[DataMember]
public string street;
}
El siguiente código crea un pedido de compra.
'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
//Construct a purchase order:
Address adr = new Address();
adr.street = "123 Main St.";
PurchaseOrder po = new PurchaseOrder();
po.billTo = adr;
po.shipTo = adr;
Observe que los campos billTo
y shipTo
están establecidos en la misma instancia de objeto. Sin embargo, el XML generado duplica la información duplicada y parece similar al siguiente XML.
<PurchaseOrder>
<billTo><street>123 Main St.</street></billTo>
<shipTo><street>123 Main St.</street></shipTo>
</PurchaseOrder>
Sin embargo, este enfoque tiene las siguientes características, que puede que no se deseen:
Rendimiento. La replicación de datos es ineficaz.
Referencias circulares. Si los objetos hacen referencia a ellos mismos, incluso a través de otros objetos, la serialización mediante resultados de replicación resulta en un bucle infinito. (Si esto sucede, el serializador inicia una SerializationException.)
Semántica. A veces es importante conservar el hecho de que se realicen dos referencias al mismo objeto, y no a dos objetos idénticos.
Por estas razones, algunas sobrecargas del constructor de DataContractSerializer tienen un parámetro preserveObjectReferences (el valor predeterminado es false). Cuando este parámetro está establecido en true, se usa un método especial para la codificación de referencias a objetos, que solo WCF entiende. Cuando se establece en true, el ejemplo de código XML tiene ahora el siguiente aspecto.
<PurchaseOrder ser:id="1">
<billTo ser:id="2"><street ser:id="3">123 Main St.</street></billTo>
<shipTo ser:ref="2"/>
</PurchaseOrder>
El espacio de nombres "ser" hace referencia al espacio de nombres de serialización estándar, https://schemas.microsoft.com/2003/10/Serialization/. Cada parte de datos se serializa solo una vez y se proporciona un número de identificador a cada una de esas partes de datos, y los posteriores usos resultan en una referencia a los datos ya serializados.
Nota: |
---|
Si los atributos "id" y "ref" están presentes en el elemento XMLElement del contrato de datos, se observa el atributo "ref" y se omite "id". |
Es importante conocer las limitaciones de este modo:
El XML que produce el DataContractSerializer con preserveObjectReferences establecido en true no es interoperable con ninguna otra tecnología y solo puede obtenerse acceso a él mediante otra instancia del DataContractSerializer, también con preserveObjectReferences establecido en true.
No hay compatibilidad con metadatos (esquema) para esta característica. El esquema que se genera solo es válido para el caso en que preserveObjectReferences está establecido en false.
Esta característica puede hacer que el proceso de serialización y deserialización se ejecuten más lentamente. Aunque los datos no tienen que replicarse, las comparaciones con objetos adicionales se deben realizar en este modo.
Precaución: |
---|
Cuando se habilita el modo preserveObjectReferences, es especialmente importante establecer el valor maxItemsInObjectGraph en la cuota correcta. Debido a la manera en la que se administran las matrices en este modo, es fácil que un atacante construya un pequeño mensaje malintencionado que provoque un uso de memoria grande limitado únicamente por la cuota maxItemsInObjectGraph. |
Especificación de un contrato de datos suplente
Algunas sobrecargas del constructor del DataContractSerializer tienen un parámetro dataContractSurrogate, que se puede establecer en null. De lo contrario, puede utilizarlo para especificar un contrato de datos suplente, que es un tipo que implementa la interfaz IDataContractSurrogate. Puede utilizar a continuación la interfaz para personalizar el proceso de serialización y deserialización. Para obtener más información, vea Suplentes de contratos de datos.
Serialización
La siguiente información se aplica a cualquier clase que herede del XmlObjectSerializer,incluido las clases DataContractSerializer y NetDataContractSerializer.
Serialización simple
La manera más básica de serializar un objeto es pasarlo al método WriteObject. Hay tres sobrecargas, que permiten escribir en una Stream, un XmlWriter o un XmlDictionaryWriter. Con la sobrecarga Stream, el resultado es XML en la codificación UTF-8. Con la sobrecarga XmlDictionaryWriter, el serializador optimiza su resultado para XML binario.
Al utilizar el método WriteObject, el serializador utiliza el nombre y espacio de nombres predeterminado para el elemento contenedor y lo escribe junto con el contenido (vea la sección anterior "Especificación del espacio de nombres y el nombre de raíz predeterminados").
En el ejemplo siguiente se muestra la escritura con XmlDictionaryWriter.
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)
Person p = new Person();
DataContractSerializer dcs =
new DataContractSerializer(typeof(Person));
XmlDictionaryWriter xdw =
XmlDictionaryWriter.CreateTextWriter(someStream,Encoding.UTF8 );
dcs.WriteObject(xdw, p);
Esto produce un XML similar al siguiente.
<Person>
<Name>Jay Hamlin</Name>
<Address>123 Main St.</Address>
</Person>
Serialización paso a paso
Utilice los métodos WriteStartObject, WriteObjectContenty WriteEndObject para escribir el elemento de fin, escribir el contenido del objeto y cerrar el elemento contenedor, respectivamente.
Nota: |
---|
No hay ninguna sobrecarga Stream de estos métodos. |
Esta serialización paso a paso tiene dos usos extendidos. Uno consiste en insertar contenido como atributos o comentarios entre WriteStartObject y WriteObjectContent, tal y como se muestra en el siguiente ejemplo.
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);
Esto produce un XML similar al siguiente.
<Person serializedBy="myCode">
<Name>Jay Hamlin</Name>
<Address>123 Main St.</Address>
</Person>
Otro uso común consiste en evitar utilizar WriteStartObject y WriteEndObject por completo, y escribir su propio elemento contenedor personalizado (o incluso pasar por alto totalmente la escritura de un contenedor), tal y como se muestra en el siguiente código.
xdw.WriteStartElement("MyCustomWrapper")
dcs.WriteObjectContent(xdw, p)
xdw.WriteEndElement()
xdw.WriteStartElement("MyCustomWrapper");
dcs.WriteObjectContent(xdw, p);
xdw.WriteEndElement();
Esto produce un XML similar al siguiente.
<MyCustomWrapper>
<Name>Jay Hamlin</Name>
<Address>123 Main St.</Address>
</MyCustomWrapper>
Nota: |
---|
El uso de la serialización paso a paso puede producir un XML de esquema no válido. |
Deserialización
La siguiente información se aplica a cualquier clase que herede del XmlObjectSerializer,incluido las clases DataContractSerializer y NetDataContractSerializer.
La manera más básica de deserializar un objeto consiste en llamar a una de las sobrecargas del método ReadObject. Hay tres sobrecargas, que permiten leer con un XmlDictionaryReader, un XmlReadero una Stream. Observe que la sobrecarga Stream crea un XmlDictionaryReader textual que no está protegido por ninguna cuota, por lo que solo debería utilizarse para leer datos de confianza.
También tenga en cuenta que el objeto que devuelve el método ReadObject debe convertirse al tipo adecuado.
El siguiente código construye una instancia de DataContractSerializer y XmlDictionaryReader, a continuación, deserializa una instancia de Person
.
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)
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);
Antes de llamar al método ReadObject, coloque el lector XML en el elemento contenedor o en un nodo de no contenido que preceda al elemento contenedor. Puede realizar esto llamando al método Read del XmlReader o su derivación y probando NodeType, tal y como se muestra en el código siguiente.
Dim ser As New DataContractSerializer(GetType(Person), "Customer", "https://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
DataContractSerializer ser = new DataContractSerializer(typeof(Person),
"Customer", @"https://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;
}
}
Observe que puede leer atributos en este elemento contenedor antes de entregar el lector a ReadObject
.
Al utilizar una de las sobrecargas simples ReadObject
, el deserializador busca el nombre y espacio de nombres predeterminado en el elemento contenedor (consulte la sección anterior, "Especificación del nombre y espacio de nombres raíz predeterminado") e inicia una excepción si encuentra un elemento desconocido. En el ejemplo anterior se espera el elemento contenedor <Person>
. Se llama al método IsStartObject para comprobar que el lector está ubicado en un elemento que se denomina como esperado.
Hay una manera de deshabilitar esta comprobación de nombre del elemento contenedor; algunas sobrecargas del método ReadObject toman el parámetro booleano verifyObjectName, que está establecido de forma predeterminada en true. Cuando se establece en false, se ignoran el nombre y el espacio de nombres del elemento contenedor. Esto es útil para leer XML escrito mediante el mecanismo de serialización paso a paso descrito previamente.
Uso del NetDataContractSerializer
La principal diferencia entre DataContractSerializer y NetDataContractSerializer es que DataContractSerializer utiliza nombres de contratos de datos, mientras que NetDataContractSerializer genera un completo ensamblado .NET Framework y nombres de tipos en el XML serializado. Esto significa que se han de compartir exactamente los mismos tipos entre los extremos de serialización y deserialización. Esto significa que el mecanismo de tipos conocidos no se requiere con el NetDataContractSerializer, porque siempre se conocen los tipos exactos que se van a deserializar.
Sin embargo, pueden producirse varios problemas:
Seguridad. Se carga cualquier tipo en el XML que se esté deserializando. Esto se puede explotar para forzar la carga de tipos malintencionados. El uso del NetDataContractSerializer con datos que no son de confianza solo se debería realizar si se utiliza un Enlazador de Serialización (mediante el parámetro del constructor o la propiedad Binder). El enlazador solo permite cargar tipos seguros. El mecanismo del enlazador es idéntico al que utilizan los tipos en el espacio de nombres de System.Runtime.Serialization.
Control de versiones El uso de nombres de ensamblado y tipos completos en el XML restringe en gran medida el control de versión de los tipos. No se pueden cambiar lo siguientes elementos: nombres de tipos, espacios de nombres, nombres de ensamblados y versiones de ensamblados. Establecer la propiedad AssemblyFormat o el parámetro de constructor en Simple en lugar del valor predeterminado de Full permite los cambios de versión de ensamblado, pero no para tipos de parámetros genéricos.
Interoperabilidad. Dado que los nombres de ensamblado y el tipo de .NET Framework se incluyen en el XML, solo la plataforma .NET Framework puede tener acceso a los datos resultantes.
Rendimiento. Escribir los nombres del ensamblado y el tipo aumenta significativamente el tamaño del XML resultante.
Este mecanismo es similar a la serialización binaria o de SOAP utilizada por la comunicación remota de .NET Framework (específicamente, BinaryFormatter y SoapFormatter).
El uso del NetDataContractSerializer es similar al uso del DataContractSerializer, con las siguientes diferencias:
Los constructores no le exigen que especifique un tipo de raíz. Puede serializar cualquier tipo con la misma instancia del NetDataContractSerializer.
Los constructores no aceptan ninguna lista de tipos conocidos. El mecanismo de tipos conocidos no es necesario si los nombres de tipos se serializan en el XML.
Los constructores no aceptan un contrato de datos suplente. En su lugar, aceptan un parámetro ISurrogateSelector llamado surrogateSelector (que asigna a la propiedad SurrogateSelector). Éste es un mecanismo suplente de herencia.
Los constructores aceptan un parámetro denominado assemblyFormat del FormatterAssemblyStyle que asigna a la propiedad AssemblyFormat. Tal y como se comentó previamente, esto se puede utilizar para mejorar las capacidades del control de versiones del serializador. Esto es idéntico al mecanismo FormatterAssemblyStyle en la serialización binaria o de SOAP.
Los constructores aceptan un parámetro StreamingContext denominado context que asigna a la propiedad Context. Puede utilizar esto para pasar información en los tipos que se serializan. Este uso es idéntico al del mecanismo StreamingContext utilizado en otras clases System.Runtime.Serialization.
Los métodos Serialize y Deserialize son alias de los métodos WriteObject y ReadObject. Éstos existen para proporcionar un modelo de programación con serialización binaria o de SOAP más coherente.
Para obtener más información sobre estas características, vea Binary Serialization.
Generalmente, los formatos XML que usan el NetDataContractSerializer y el DataContractSerializer no son compatibles. Es decir, intentar serializar con uno de estos serializadores y deserializar con el otro no es un escenario admitido.
Asimismo, observe que el NetDataContractSerializer no genera el nombre de ensamblado y tipo .NET Framework completo para cada nodo del gráfico de objeto. Solo genera esa información cuando hay ambigüedad. Es decir, produce el resultado en el nivel del objeto raíz y para cualquier caso polimórfico.
Vea también
Referencia
DataContractSerializer
NetDataContractSerializer
XmlObjectSerializer
Conceptos
Tipos admitidos por el serializador de contrato de datos