¿Obstáculos de serialización XML .NET?

Me he encontrado con algunas trampas al hacer la serialización de C # XML que pensaba compartir:

  • No puede serializar elementos que son de solo lectura (como KeyValuePairs)
  • No puede serializar un diccionario genérico. En su lugar, intente con esta clase contenedora (desde http://weblogs.asp.net/pwelter34/archive/2006/05/03/444961.aspx ):

using System; using System.Collections.Generic; using System.Text; using System.Xml.Serialization; [XmlRoot("dictionary")] public class SerializableDictionary : Dictionary, IXmlSerializable { public System.Xml.Schema.XmlSchema GetSchema() { return null; } public void ReadXml(System.Xml.XmlReader reader) { XmlSerializer keySerializer = new XmlSerializer(typeof(TKey)); XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue)); bool wasEmpty = reader.IsEmptyElement; reader.Read(); if (wasEmpty) return; while (reader.NodeType != System.Xml.XmlNodeType.EndElement) { reader.ReadStartElement("item"); reader.ReadStartElement("key"); TKey key = (TKey)keySerializer.Deserialize(reader); reader.ReadEndElement(); reader.ReadStartElement("value"); TValue value = (TValue)valueSerializer.Deserialize(reader); reader.ReadEndElement(); this.Add(key, value); reader.ReadEndElement(); reader.MoveToContent(); } reader.ReadEndElement(); } public void WriteXml(System.Xml.XmlWriter writer) { XmlSerializer keySerializer = new XmlSerializer(typeof(TKey)); XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue)); foreach (TKey key in this.Keys) { writer.WriteStartElement("item"); writer.WriteStartElement("key"); keySerializer.Serialize(writer, key); writer.WriteEndElement(); writer.WriteStartElement("value"); TValue value = this[key]; valueSerializer.Serialize(writer, value); writer.WriteEndElement(); writer.WriteEndElement(); } } } 

¿Hay otros atracos de serialización XML por ahí?

Otra gran sorpresa: cuando se genera XML a través de una página web (ASP.NET), no se debe incluir la marca de pedido de bytes Unicode . Por supuesto, las formas de usar o no la BOM son casi las mismas:

MALO (incluye BOM):

 XmlTextWriter wr = new XmlTextWriter(stream, new System.Text.Encoding.UTF8); 

BUENO:

 XmlTextWriter wr = new XmlTextWriter(stream, new System.Text.UTF8Encoding(false)) 

Puede pasar falso de forma explícita para indicar que no desea la lista de materiales. Observe la clara y obvia diferencia entre Encoding.UTF8 y UTF8Encoding .

Los tres bytes BOM adicionales al principio son (0xEFBBBF) o (239 187 191).

Referencia: http://chrislaco.com/blog/troubleshooting-common-problems-with-the-xmlserializer/

Todavía no puedo hacer comentarios, así que comentaré sobre la publicación de Dr8k y haré otra observación. Las variables privadas que están expuestas como propiedades getter / setter públicas, y se serializan / deserializan como tales a través de esas propiedades. Lo hicimos en mi antiguo trabajo al mismo tiempo.

Sin embargo, una cosa a tener en cuenta es que si tiene alguna lógica en esas propiedades, la lógica se ejecuta, por lo que, a veces, el orden de la serialización realmente importa. Los miembros están ordenados implícitamente por la forma en que están ordenados en el código, pero no hay garantías, especialmente cuando heredas otro objeto. Ordenarlos explícitamente es un dolor en la retaguardia.

Me he quemado por esto en el pasado.

Al serializar en una cadena XML desde una secuencia de memoria, asegúrese de usar MemoryStream # ToArray () en lugar de MemoryStream # GetBuffer () o terminará con caracteres basura que no se deserializarán correctamente (debido al búfer adicional asignado).

http://msdn.microsoft.com/en-us/library/system.io.memorystream.getbuffer(VS.80).aspx

Si el serializador encuentra un miembro / propiedad que tiene una interfaz como tipo, no se serializará. Por ejemplo, lo siguiente no se serializará en XML:

 public class ValuePair { public ICompareable Value1 { get; set; } public ICompareable Value2 { get; set; } } 

Aunque esto serializará:

 public class ValuePair { public object Value1 { get; set; } public object Value2 { get; set; } } 

IEnumerables que se generan a través de rendimientos no son serializables. Esto se debe a que el comstackdor genera una clase separada para implementar el retorno de rendimiento y esa clase no está marcada como serializable.

No puede serializar las propiedades de solo lectura. Debe tener un getter y un setter, incluso si nunca tiene la intención de utilizar la deserialización para convertir XML en un objeto.

Por la misma razón, no puede serializar propiedades que devuelven interfaces: el deserializador no sabría qué clase concreta crear.

Oh, este es uno bueno: dado que el código de serialización XML se genera y se coloca en una DLL separada, no se obtiene ningún error significativo cuando hay un error en el código que rompe el serializador. Algo así como “no se puede localizar s3d3fsdf.dll”. Bonito.

Una cosa más a tener en cuenta: no puede serializar miembros de clase privada / protegida si está utilizando la serialización XML “predeterminada”.

Pero puede especificar la lógica personalizada de serialización XML implementando IXmlSerializable en su clase y serializar cualquier campo privado que necesite / desee.

http://msdn.microsoft.com/en-us/library/system.xml.serialization.ixmlserializable.aspx

No se puede serializar un objeto que no tiene un construtor sin parámetros (acaba de ser mordido por ese).

Y por alguna razón, a partir de las siguientes propiedades, Value se serializa, pero no FullName:

  public string FullName { get; set; } public double Value { get; set; } 

Nunca llegué a averiguar por qué, simplemente cambié el valor a interno …

Si su ensamblado generado de Serialización XML no está en el mismo contexto de carga que el código que intenta usarlo, se encontrará con errores increíbles como:

 System.InvalidOperationException: There was an error generating the XML document. ---System.InvalidCastException: Unable to cast object of type 'MyNamespace.Settings' to type 'MyNamespace.Settings'. at Microsoft.Xml.Serialization.GeneratedAssembly. XmlSerializationWriterSettings.Write3_Settings(Object o) 

La causa de esto para mí fue un complemento cargado usando el contexto LoadFrom, que tiene muchas desventajas al usar el contexto Cargar. Bastante divertido rastreando eso.

Puede tener problemas para serializar objetos de tipo Color y / o Fuente.

Estos son los consejos que me ayudaron:

http://www.codeproject.com/KB/XML/xmlsettings.aspx

http://www.codeproject.com/KB/cs/GenericXmlSerializition.aspx

Consulte ” Compatibilidad de enlace de atributos de idioma de definición de esquemas XML avanzados ” para obtener más información acerca de qué es compatible con el serializador XML, y para obtener detalles sobre la forma en que se admiten las características compatibles de XSD.

Si intenta serializar una matriz, List o IEnumerable que contiene instancias de subclases de T , debe usar XmlArrayItemAttribute para enumerar todos los subtipos que se utilizan. De lo contrario, obtendrás una System.InvalidOperationException inútil en el tiempo de ejecución cuando serialices.

Aquí hay parte de un ejemplo completo de la documentación

 public class Group { /* The XmlArrayItemAttribute allows the XmlSerializer to insert both the base type (Employee) and derived type (Manager) into serialized arrays. */ [XmlArrayItem(typeof(Manager)), XmlArrayItem(typeof(Employee))] public Employee[] Employees; 

Las variables / propiedades privadas no están serializadas en el mecanismo predeterminado para la serialización de XML, pero están en serialización binaria.

Las propiedades marcadas con el atributo Obsolete no están serializadas. No probé con el atributo Deprecated pero supongo que actuaría de la misma manera.

Realmente no puedo explicar esto, pero descubrí que esto no se serializará:

 [XmlElement("item")] public myClass[] item { get { return this.privateList.ToArray(); } } 

pero esto:

 [XmlElement("item")] public List item { get { return this.privateList; } } 

Y también vale la pena señalar que si está serializando en un flujo de memoria, es posible que desee buscar 0 antes de usarlo.

Tenga cuidado con los tipos de serialización sin serialización explícita, puede ocasionar retrasos mientras .Net los construye. Descubrí esto recientemente al serializar RSAParameters .

Si su XSD hace uso de grupos de sustitución, es probable que no pueda (de) serializarlo automáticamente. Tendrá que escribir sus propios serializadores para manejar este escenario.

P.ej.

                          

En este ejemplo, un sobre puede contener mensajes. Sin embargo, el serializador predeterminado de .NET no distingue entre Message, ExampleMessageA y ExampleMessageB. Solo se serializará desde y hacia la clase de mensaje base.

Las variables / propiedades privadas no se serializan en serialización XML, sino que están en serialización binaria.

Creo que esto también lo atrapa si está exponiendo a los miembros privados a través de propiedades públicas: los miembros privados no se serializan, por lo que los miembros públicos hacen referencia a valores nulos.