2009-03-10 25 views
9

La serialización XML en .NET permite objetos polimórficos a través del parámetro extraTypes[] del constructor XmlSerializer. También permite la personalización de la serialización XML para los tipos que implementan IXmlSerializable.Tipos polimórficos e IXmlSerializable

Sin embargo, no soy capaz de combinar estas dos características - como se demuestra en este ejemplo mínima:

using System; 
using System.IO; 
using System.Xml; 
using System.Xml.Schema; 
using System.Xml.Serialization; 

namespace CsFoo 
{ 
    public class CustomSerializable : IXmlSerializable 
    { 
     public XmlSchema GetSchema() { return null; } 
     public void ReadXml(XmlReader xr) { } 
     public void WriteXml(XmlWriter xw) { } 
    } 

    class CsFoo 
    { 
     static void Main() 
     { 
      XmlSerializer xs = new XmlSerializer(
       typeof(object), 
       new Type[] { typeof(CustomSerializable) }); 

     xs.Serialize(new StringWriter(), new CustomSerializable()); 
    } 
} 

La última línea de tiros System.InvalidOperationException con este mensaje:

El tipo CsFoo.CustomSerializable no se puede usar en este contexto para usar CsFoo.CustomSerializable como parámetro, devolver tipo, o miembro de una clase o estructura, el parámetro, devolver tipo, o el miembro se debe declarar como ty pe CsFoo.CustomSerializable (no puede ser objeto). Los objetos de tipo CsFoo.CustomSerializable no se pueden usar en colecciones sin tipeo, como ArrayLists.

vadear a través de los conjuntos XML generados dinámicamente, en última instancia, nos volvemos a código de la biblioteca estándar de .NET llamando:

System.Xml.Serialization.XmlSerializationWriter.WriteTypedPrimitive(
    String, String, Object, Boolean) : Void 

A su vez, esto conduce a:

protected Exception CreateUnknownTypeException(Type type) 
{ 
    if (typeof(IXmlSerializable).IsAssignableFrom(type)) 
    { 
     return new InvalidOperationException(
      Res.GetString("XmlInvalidSerializable", 
      new object[] { type.FullName })); 
    } 

    // Rest omitted... 

espectáculos Reflector que el recurso XmlInvalidSerializable se corresponde con la cadena anterior, es decir, WriteTypedPrimitive no le gusta IXmlSerializable.

Si generamos un serializador no polimórficos, así:

XmlSerializer xs = new XmlSerializer(typeof(CustomSerializable)); 

.NET generará una llamada a:

System.Xml.Serialization.XmlSerializationWriter.WriteSerializable(
    IXmlSerializable, String, String, Boolean) : Void 

Este se encarga de IXmlSerializable correctamente. ¿Alguien sabe por qué .NET no usa esta función en el caso polimórfico? Mirando el C# que genera el serializador XML, me parece que esto se puede hacer con bastante facilidad. Aquí hay un código que obtuve del serializador XML, con una solución probada:

void Write1_Object(string n, string ns, global::System.Object o, 
    bool isNullable, bool needType) 
{ 
    if ((object)o == null) 
    { 
     if (isNullable) WriteNullTagLiteral(n, ns); 
     return; 
    } 
    if (!needType) 
    { 
     System.Type t = o.GetType(); 
     if (t == typeof(global::System.Object)) 
     { 
     } 
>>> patch begin <<< 
+   else if (typeof(IXmlSerializable).IsAssignableFrom(t)) 
+   { 
+    WriteSerializable((System.Xml.Serialization.IXmlSerializable) 
        ((global::CsFoo.CustomSerializable)o), 
+     @"CustomSerializable", @"", true, true); 
+   } 
>>> patch end <<< 
     else 
     { 
      WriteTypedPrimitive(n, ns, o, true); 
      return; 
     } 
    } 
    WriteStartElement(n, ns, o, false, null); 
    WriteEndElement(o); 
} 

Es éste queda fuera por razones técnicas o simplemente una limitación característica? Característica no admitida, o mi idiotez Mis intertubes habilidades de Google me fallan.

Encontré algunas preguntas relacionadas aquí, con "C# Xml-Serializing a derived class using IXmlSerializable" siendo más relevante. Me lleva a creer que simplemente no es posible.

En ese caso, mi pensamiento actual es inyectar una implementación predeterminada IXmlSerializable en la clase base raíz. Entonces todo será un IXmlSerializable, y .NET no se quejará. Puedo usar Reflection.Emit para batir los cuerpos ReadXml y WriteXml para cada tipo concreto, generando XML que se vería igual a como lo haría si usara el de la biblioteca.

Algunas personas, al enfrentarse con un problema de serialización XML, piensan "Lo sé, usaré Reflection.Emit para generar código". Ahora ellos tienen dos problemas.


P.S. Nota; Conozco las alternativas a la serialización XML de .NET, y sé que tiene limitaciones. También sé que guardar un POCO es mucho más simple que tratar con tipos de datos abstractos. Pero tengo un montón de código heredado y necesito soporte para los esquemas XML existentes.

Así que aunque aprecio las respuestas que muestran lo fácil que es en SomeOtherXML, YAML, XAML, ProtocolBuffers, DataContract, RandomJsonLibrary, Thrift, o en su biblioteca MorseCodeBasedSerializeToMp3 - Hey yo podría aprender algo -, lo que estoy esperando es una Solución de problemas del serializador de XML, si no.

+0

Aunque es una adición bastante tardía, de todos modos: la solución con tipos adicionales no funciona para mí en el caso de IXmlSerializable. Sin embargo [este enfoque] (http://www.softwarerockstar.com/2006/12/using-ixmlserializable-to-overcome-not-expected-error-on-derived-classes/) está funcionando, ya que permite la clase deserializada ser diferente de la clase de propiedad (!), por lo que cualquier lógica personalizada que encuentre el tipo correcto para crear se puede implementar de esa manera. – Vlad

+0

Hay otra solución declarada [aquí] (https://connect.microsoft.com/VisualStudio/feedback/details/422577/incorrect-deserialization-of-polymorphic-type-that-implements-ixmlserializable) (utilizando el atributo 'XmlSchemaProvider' para enlazar tipos xml a los tipos .net), pero carece del código, y no pude hacer que esa idea funcionara. ¿Nadie? – Vlad

Respuesta

2

que era capaz de reproducir su problema, cuando se utiliza object:

XmlSerializer xs = new XmlSerializer(
    typeof(object), 
    new Type[] { typeof(CustomSerializable) }); 

Sin embargo, a continuación, creé una clase derivada de CustomSerializable:

public class CustomSerializableDerived : CustomSerializable 
{ 
} 

y trató de serializarlo:

XmlSerializer xs = new XmlSerializer(
    typeof(CustomSerializable), 
    new Type[] { typeof(CustomSerializableDerived) }); 

Esto funcionó.

Por lo tanto, parece que el problema está restringido al caso en que especifica "objeto" como el tipo para serializar, pero no si especifica un tipo base concreto.

Haré más investigación sobre esto en la mañana.

0

Para que IxmlSerializable funcione, la clase necesita tener un constructor nulo.

Considérese una clase base

  • MyBaseXmlClass: IXmlSerializable
    - aplicar GetSchema
  • MyXmlClass: MyBaseXmlClass
    - ReadXml
    • WriteXml
+0

-1: Su tiene un constructor predeterminado; podría usar código real en lugar de solo texto para ilustrar su punto. –

1

En primer lugar comunicado carteles

La serialización XML en .NET permite que los objetos polimórficos a través de los extraTypes [] parámetro del constructor XmlSerializer.

es engañoso. Según MSDN extratypes se utiliza para:

Si un o campo propiedad devuelve un array, el parámetro extraTypes especifica objetos que se pueden insertar en la matriz.

Lo que significa que si en algún lugar de su objeto serializado se devuelven los objetos polimórficos a través de una matriz, se pueden procesar.

Si bien no he encontrado realmente una solución para serializar un tipo polimórfico como objeto raíz de XML, pude serializar los tipos polimórficos encontrados en el gráfico de objetos usando el serializador XML estándar o IXmlSerializable. Véase más abajo para mi solución:

using System.IO; 
using System.Xml; 
using System.Xml.Schema; 
using System.Xml.Serialization; 

namespace CsFoo 
{ 
    public class Foo 
    { 
    [XmlElement("BarStandard", typeof(BarStandardSerializable))] 
    [XmlElement("BarCustom", typeof(BarCustomSerializable))] 
    public Bar BarProperty { get; set; } 
    } 

    public abstract class Bar 
    { } 

    public class BarStandardSerializable : Bar 
    { } 

    public class BarCustomSerializable : Bar, IXmlSerializable 
    { 
    public XmlSchema GetSchema() { return null; } 
    public void ReadXml(XmlReader xr) { } 
    public void WriteXml(XmlWriter xw) { } 
    } 

    class CsFoo 
    { 
    static void Main() 
    { 
     StringWriter sw = new StringWriter(); 
     Foo f1 = new Foo() { BarProperty = new BarCustomSerializable() }; 
     XmlSerializer xs = new XmlSerializer(typeof(Foo)); 

     xs.Serialize(sw, f1); 
     StringReader sr= new StringReader(sw.ToString()); 
     Foo f2 = (Foo)xs.Deserialize(sr); 
    } 
    } 
} 

Tenga en cuenta que el uso de cualquiera

XmlSerializer xs = new XmlSerializer(typeof(Foo), 
      new Type[] { typeof(BarStandardSerializable), 
      typeof(BarCustomSerializable)}); 

o

[XmlInclude(typeof(BarCustomSerializable))] 
[XmlInclude(typeof(BarStandardSerializable))] 
public abstract class Bar 
{ } 

sin XmlElement definido, haría que el código para fallar en la serialización.

+0

Nota para futuros lectores: si 'BarProperty' es algo así como una matriz' [XmlArrayItem (/ * ... * /)] 'debe usarse en lugar de' [XmlElement (/ * ... * /)] ' –

Cuestiones relacionadas