Pregunta

A raíz de mi la pregunta anterior He estado trabajando en conseguir mi modelo de objeto a serializar a XML.Pero ahora tengo ejecutar en un problema (quelle sorpresa!).

El problema que tengo es que tengo una colección, que es de una clase base abstracta, que es poblada por los concretos tipos derivados.

Pensé que estaría bien añadir los atributos XML para todas las clases involucradas y todo estaría correcto.Lamentablemente, ese no es el caso!

Por lo que he hecho algo de investigación en Google y ahora entiendo por qué no es de trabajo.En que el XmlSerializer es, de hecho, haciendo algunas inteligente reflexión para serializar objetos/XML, y desde su base en el tipo abstracto, no puede averiguar qué diablos está hablando a.Bien.

Hice venir a través de esta página en CodeProject, que parece que puede ayudar mucho (sin embargo, para leer/consumir totalmente), pero pensé que me gustaría llevar este problema a la StackOverflow mesa, para ver si usted tiene cualquier limpio hacks/trucos para hacer que esta en marcha y funcionando en la forma más rápida/más ligero posible.

Una cosa también debo agregar es que me NO quiero ir por el XmlInclude de la ruta.No es simplemente demasiado enganche con ella, y en esta zona del sistema está bajo un fuerte desarrollo, por lo que el sería un verdadero dolor de cabeza de mantenimiento!

¿Fue útil?

Solución

Problema Resuelto!

OK, así que finalmente llegó allí (es cierto que con un mucho de la ayuda de aquí!).

Para resumir:

Objetivos:

  • Yo no quería ir por el XmlInclude ruta debido a que el mantenimiento de dolor de cabeza.
  • Una vez que se encontró una solución, yo quería que fuera rápido para implementar en otras aplicaciones.
  • Colecciones de tipos Abstractos pueden ser utilizados, así como a las propiedades abstractas.
  • Yo realmente no quería molestar con hacer cosas "especial" en las clases concretas.

Se identificaron los Temas/aspectos a tener en cuenta:

  • XmlSerializer hace algunos bastante fresco a la reflexión, pero es muy limitado cuando se trata de tipos abstractos (es decir,sólo funcionará con las instancias del tipo abstracto en sí mismo, no subclases).
  • El atributo Xml decoradores definir cómo el XmlSerializer trata de las propiedades de sus hallazgos.El tipo físico también puede ser especificado, pero esto crea un acoplamiento entre la clase y el serializador (no es bueno).
  • Podemos implementar nuestro propio XmlSerializer mediante la creación de una clase que implementa IXmlSerializable .

La Solución

He creado una clase genérica en la que se especifique el tipo genérico como el tipo abstracto que va a trabajar.Esto le da a la clase la capacidad de "traducir" entre el tipo abstracto y el concreto tipo de puesto que se puede codificar la fundición (es decir,podemos obtener más información de la clase XmlSerializer puede).

Luego he aplicado el IXmlSerializable la interfaz, esto es bastante sencillo, pero cuando se serializan necesitamos para asegurar que escribir el tipo de la clase concreta para el XML, de modo que podemos poner de vuelta cuando de-serializar.También es importante tener en cuenta que debe ser totalmente cualificado como a las asambleas de que las dos clases son probablemente diferentes.Por supuesto, hay un poco de comprobación de tipos y cosas que suceden aquí.

Desde el XmlSerializer no puede emitir, tenemos que proporcionar el código para hacer eso, por lo que el operador implícito es entonces sobrecargado (yo ni siquiera sabía que se podía hacer esto!).

El código para el AbstractXmlSerializer es este:

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;

namespace Utility.Xml
{
    public class AbstractXmlSerializer<AbstractType> : IXmlSerializable
    {
        // Override the Implicit Conversions Since the XmlSerializer
        // Casts to/from the required types implicitly.
        public static implicit operator AbstractType(AbstractXmlSerializer<AbstractType> o)
        {
            return o.Data;
        }

        public static implicit operator AbstractXmlSerializer<AbstractType>(AbstractType o)
        {
            return o == null ? null : new AbstractXmlSerializer<AbstractType>(o);
        }

        private AbstractType _data;
        /// <summary>
        /// [Concrete] Data to be stored/is stored as XML.
        /// </summary>
        public AbstractType Data
        {
            get { return _data; }
            set { _data = value; }
        }

        /// <summary>
        /// **DO NOT USE** This is only added to enable XML Serialization.
        /// </summary>
        /// <remarks>DO NOT USE THIS CONSTRUCTOR</remarks>
        public AbstractXmlSerializer()
        {
            // Default Ctor (Required for Xml Serialization - DO NOT USE)
        }

        /// <summary>
        /// Initialises the Serializer to work with the given data.
        /// </summary>
        /// <param name="data">Concrete Object of the AbstractType Specified.</param>
        public AbstractXmlSerializer(AbstractType data)
        {
            _data = data;
        }

        #region IXmlSerializable Members

        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null; // this is fine as schema is unknown.
        }

        public void ReadXml(System.Xml.XmlReader reader)
        {
            // Cast the Data back from the Abstract Type.
            string typeAttrib = reader.GetAttribute("type");

            // Ensure the Type was Specified
            if (typeAttrib == null)
                throw new ArgumentNullException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
                    "' because no 'type' attribute was specified in the XML.");

            Type type = Type.GetType(typeAttrib);

            // Check the Type is Found.
            if (type == null)
                throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
                    "' because the type specified in the XML was not found.");

            // Check the Type is a Subclass of the AbstractType.
            if (!type.IsSubclassOf(typeof(AbstractType)))
                throw new InvalidCastException("Unable to Read Xml Data for Abstract Type '" + typeof(AbstractType).Name +
                    "' because the Type specified in the XML differs ('" + type.Name + "').");

            // Read the Data, Deserializing based on the (now known) concrete type.
            reader.ReadStartElement();
            this.Data = (AbstractType)new
                XmlSerializer(type).Deserialize(reader);
            reader.ReadEndElement();
        }

        public void WriteXml(System.Xml.XmlWriter writer)
        {
            // Write the Type Name to the XML Element as an Attrib and Serialize
            Type type = _data.GetType();

            // BugFix: Assembly must be FQN since Types can/are external to current.
            writer.WriteAttributeString("type", type.AssemblyQualifiedName);
            new XmlSerializer(type).Serialize(writer, _data);
        }

        #endregion
    }
}

Así, a partir de ahí, ¿cómo le decimos a la clase XmlSerializer para trabajar con nuestros serializador en lugar de la predeterminada?Debemos pasar nuestro tipo dentro de los atributos Xml tipo de propiedad, por ejemplo:

[XmlRoot("ClassWithAbstractCollection")]
public class ClassWithAbstractCollection
{
    private List<AbstractType> _list;
    [XmlArray("ListItems")]
    [XmlArrayItem("ListItem", Type = typeof(AbstractXmlSerializer<AbstractType>))]
    public List<AbstractType> List
    {
        get { return _list; }
        set { _list = value; }
    }

    private AbstractType _prop;
    [XmlElement("MyProperty", Type=typeof(AbstractXmlSerializer<AbstractType>))]
    public AbstractType MyProperty
    {
        get { return _prop; }
        set { _prop = value; }
    }

    public ClassWithAbstractCollection()
    {
        _list = new List<AbstractType>();
    }
}

Aquí usted puede ver, tenemos una colección y una sola propiedad que se está expuesto, y todo lo que tenemos que hacer es añadir el tipo de nombre de parámetro para la declaración Xml, fácil!:D

NOTA:Si usted utiliza este código, realmente apreciaría un grito.También ayudará a conducir a más gente a la comunidad :)

Ahora, pero no está seguro de qué hacer con las respuestas aquí, ya que todos ellos tenían sus pro y contras.Voy a upmod los que considero fueron útiles (sin ofender a aquellos que no) y cerca de este, una vez que tengo la rep :)

Problema interesante y muy divertido para resolver!:)

Otros consejos

Una cosa a tener en cuenta es el hecho de que en el XmlSerialiser constructor puede pasar un array de tipos que el serialiser puede estar teniendo dificultades para resolver.He tenido que usar la que muy pocas veces donde una colección o conjunto complejo de datastructures necesarios para ser serializado y los tipos vivido en diferentes asambleas, etc.

XmlSerialiser Constructor con extraTypes param

EDITAR:Me gustaría añadir que este enfoque tiene la ventaja sobre XmlInclude atributos, etc que usted puede trabajar de una forma de descubrir y compilar una lista de los posibles tipos de hormigones en tiempo de ejecución y meterlos en.

En serio, un marco extensible de POCOs nunca va a serializar a XML de forma fiable.Digo esto porque puedo asegurar que alguien va a venir, extender su clase, y meter la pata para arriba.

Usted debe mirar en el uso de XAML para serializar el objeto gráficos.Está diseñado para ello, mientras que la serialización XML no lo es.

El código Xaml serializador y deserializer identificadores genéricos sin problema, de las colecciones de la base de clases e interfaces así (siempre y cuando las colecciones de las propias implementar IList o IDictionary).Hay algunas advertencias, tales como el marcado de su lectura sólo de la colección de propiedades con la DesignerSerializationAttribute, pero reelaboración de su código para manejar estos casos de esquina no es tan difícil.

Sólo una rápida actualización sobre esto, no me he olvidado!

Acaba de hacer un poco más de investigación, parece que estoy en un ganador, sólo tiene que conseguir el código ordenados.

Hasta ahora, tengo lo siguiente:

  • El XmlSeralizer es básicamente una clase que hace algunos símbolos de la reflexión sobre las clases es serializar.Determina las propiedades que se serializan basado en la Tipo de.
  • La razón se produce el problema es debido a un tipo de desajuste está ocurriendo, está a la espera de la BaseType pero, de hecho, recibe la DerivedType ..Mientras que usted puede pensar que iba a tratar polymorphically, no pues ello implicaría toda una carga adicional de reflexión y comprobación de tipos, que no está diseñado para hacer.

Este comportamiento parece ser capaz de ser anulado (código de pendiente) mediante la creación de una clase de proxy para actuar como intermediario entre el serializador.Este será, básicamente, determinar el tipo de la clase derivada y, a continuación, serializar que como normal.Esta clase de proxy, a continuación, se feed XML de la espalda hasta la línea para el serializador principal..

Mire este espacio!^_^

Es sin duda una solución a su problema, pero hay otro problema, algo que socava su intención de uso "portátil" formato XML.Lo malo que sucede cuando usted decide cambiar de clase en la próxima versión de su programa y que necesita tanto el apoyo a los formatos de serialización -- la nueva y la vieja (porque sus clientes todavía utilizar sus viejos archivos o bases de datos, o que se conecte a su servidor utilizando una versión antigua de su producto).Pero usted no puede utilizar este serializator más, porque usted utiliza

type.AssemblyQualifiedName

que se parece a

TopNamespace.SubNameSpace.ContainingClass+NestedClass, MyAssembly, Version=1.3.0.0, Culture=neutral, PublicKeyToken=b17a5c561934e089

que contiene los atributos de ensamblado y de la versión...

Ahora si intenta cambiar la versión del ensamblado, o si usted decide firmar este deserialización no va a funcionar...

He hecho cosas similares a esta.Lo que hago normalmente es asegurarse de que todos los atributos de serialización XML están en la clase concreta, y sólo tiene las propiedades de la clase llamada a través de las clases base (donde se requiera) para recuperar la información que será de/serializar cuando el serializador de llamadas en esas propiedades.Es un poco más de trabajo de codificación, pero que funciona mucho mejor que intentar forzar el serializador que acaba de hacer la cosa correcta.

Aún mejor, utilizando la notación:

[XmlRoot]
public class MyClass {
    public abstract class MyAbstract {} 
    public class MyInherited : MyAbstract {} 
    [XmlArray(), XmlArrayItem(typeof(MyInherited))] 
    public MyAbstract[] Items {get; set; } 
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top