XmlSerializer, colecciones y auto-propiedades…

Que XmlSerializer es una clase curiosa es evidente, hay multitud de maneras de controlar la serialización de un objeto y varios trucos más o menos ocultos (os recomiendo el blog de jmservera que tiene algunos posts interesantes)…

… Lo que quiero comentaros ahora es un caso que me encontré el otro día (valeeee… ayer), en concreto con las auto-propiedades que se incorporaron en C# 3.0.

En la msdn se dice que XmlSerializer es capaz de serializar propiedades ICollection o IEnumerable que sean read-only (de hecho la regla CA2227 del análisis estático se basa en esto). Pues bien, esto es cierto si entendemos como read-only que no haya setter… ni tan siquiera privado.

Es decir, esto NO funciona:

class Program
{
    static void Main(string[] args)
    {
        XmlSerializer xml = new XmlSerializer(typeof(Foo));
        using (FileStream fs = new FileStream(@"C:tempfoo.xml",
            FileMode.Open, FileAccess.ReadWrite, FileShare.None))
        {
            xml.Serialize(fs,new Foo());
            fs.Close();
        }

    }
}
public class Foo
{
    public Foo()
    {
        this.Data = new List<Bar>();
        this.Data.Add (new Bar());
        this.Data.Add (new Bar());
    }

    public List<Bar> Data { get; private set; }
    int OtherData;
}
public class Bar {}

Si intentamos serializar (o deserializar da igual) recibimos una System.InvalidOperationException (con un mensaje "No se puede generar una clase temporal (result=1). error CS0200: No se puede asignar la propiedad o el indizador ‘ConsoleApplication6.Foo.Data’ (es de sólo lectura)".

Si transformamos la clase Foo al estilo de C# 2.0 sin usar auto-propiedades, el código funciona, siempre que no pongamos el setter privado…

… como mínimo curioso, no?

Es evidente que cuando se hizo XmlSerializer nadie pensó en un setter de propiedad privado (ya que entonces lo usual era no poner setters privados en propiedades read-only), pero con la aparición de las auto-propiedades en C# 3.0 estos son cada vez más frecuentes… así que igual habría que actualizar XmlSerializer, porque que me obliguen a usar el estilo de C# 2.0 para poder serializar las propiedades de colección no es que me guste especialmente.

Y quizá, ya puestos a pedir, C# debería incorporar auto-propiedades read-only sin necesidad de poner el setter privado y que tuviesen la misma semántica que las variables readonly: sólo podrían ser inicializadas en el constructor. Esto permitiría también que el CLR realizara determinadas optimizaciones…

Saludos!! 😉

5 comentarios sobre “XmlSerializer, colecciones y auto-propiedades…”

  1. Hace algún tiempo estube pegandome con esto mismo …

    El problema no está tanto en la serializacion sino en la desserialización. ¿Como reconstruyo una propiedad publica si el setter es privado?!!!

  2. Yo en realidad ya no uso XMLSerializer. Siempre serializo con datacontract serializer que tiene lo mejor del XMLSerializer pero permite que esa entidad algun dia viaje por un servicio de WCF. lo malo es que solo lo puedo usar en proyectos que tengan el Framework 3.5. Opiniones?
    @jkpelaez
    @3Metas

  3. Hola!
    Gracias a a los dos por vuestros comentarios 🙂

    @Pedro
    Creo que a mi incluso me daba error en la serialización… Pero bueno, aunque no fuese así, el tema está en que teoricamente XmlSerializer está preparado para gestionar propiedades de colecciones que sean read-only: el NO debería intentar invocar el setter, puesto que la colección ya la creo yo en el constructor… debería limitarse a llamar el método Add() que es justamente lo que hace si no hay setter privado 🙂

    @jkpelaez
    Pues no me había pleanteado esto que propones, así que no sé… 🙂 De todos modos, muchas veces uso XmlSerializer para crearme rápidamente clases capaces de serializar XMLs que me vienen de otras aplicaciones… con formatos propios… no se datacontract serializer podría adaptarse a todos estos formatos!

    Saludos!

  4. Hola Eduard:
    Como bien dices el error sale al serializar … pero dandole vueltas llegue a la conclusion de que si lo piensas detenidamente es completamente lógico. Me explico (o lo intento 🙂 )

    Si serializamos un objeto con propiedades publicas de solo lectura, cuando se intente desserializar el objeto ¿como podrá el XmlSerializer asignar esta propiedad? Cuando llamamos a Serialize y Deserialize lo que hacemos es utilizar la reflexion para construir un el xml, por lo tanto podremos leer la propiedad pero no la podremos asignar. ¿no?

    Bien, ahora el caso que tu planteas en el que la propiedad se inicializa en el constructor – sin parametros por supuesto! – supone que es una propiedad «calculada», que se asigna en funcion de otras … entonces ¿Que sentido tiene serialzar la propiedad? ¿no es más logico marcarla con XmlIgnoreAttribute y que se vuelva a calcular al recuperar el objeto?

    Por ultimo se podría pensar es que dicha propiedad se calcula no en el constructor, sino como consecuencia de la ejecucion de un método. Bajo mi punto de visto eso es como intentar serializar un miembro privado y esa es una limitacion conocida de la serializacion xml.

    Por eso digo que finalmente me parecio «logica» la limitacion a la hora de serializar.

    Uff ¡que chapa!

  5. @Pedro

    Todo lo que dices es totalmente lógico y tiene sentido… 😉
    Pero el post no iba tan en este sentido, sino en el hecho de que el XmlSerializer se supone que tiene en cuenta las propiedades que sean colecciones (ICollection) y que sean read-only… digamos que «sabe» tratarlas especialmente: supone que la lista se crea en el constructor por lo que el XmlSerializer sabe que le basta llamar a Add o al indexer para acceder a los elementos y deserializar o serializar cada uno de ellos. Recuerda que el mismo programa de ejemplo que he puesto, si quitas el setter privado y haces la propiedad read-only al estilo de C# 2.0 funciona…

    … y este es el «live motive» del post: Que para el XmlSerializer una propiedad «read only» es una propiedad SIN setter, y no le sirve con setter privado, cuando «conceptualmente» al menos desde el punto de vista de cualquier clase externa deberían ser lo mismo… esto en mi opinión es un «bug» del XmlSerializer.

    Saludos! 😉

Responder a etomas Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *