Tipos polimórficos e IXmlSerializable

9

A serialização XML no .NET permite objetos polimórficos através do parâmetro extraTypes[] do construtor XmlSerializer . Também permite a personalização da serialização de XML para tipos que implementam IXmlSerializable .

No entanto, não consigo combinar esses dois recursos, como demonstrado neste exemplo mínimo:

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());
    }
}

A última linha lança System.InvalidOperationException com esta mensagem:

  

O tipo CsFoo.CustomSerializable não pode ser usado neste contexto para   use CsFoo.CustomSerializable como um parâmetro, return   tipo, ou membro de uma classe ou struct, o parâmetro, retornar   tipo ou membro deve ser declarado como tipo CsFoo.CustomSerializable   (não pode ser objeto). Objetos do tipo CsFoo.CustomSerializable   não pode ser usado em coleções não tipificadas, como ArrayLists.

Percorrendo os assemblies XML gerados dinamicamente, nós finalmente retornamos ao código da biblioteca padrão .NET chamando:

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

Por sua vez, isso leva a:

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

    // Rest omitted...

O reflector mostra que o recurso XmlInvalidSerializable corresponde à cadeia acima, ou seja, WriteTypedPrimitive não gosta de IXmlSerializable .

Se gerarmos um serializador não polimórfico, da seguinte forma:

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

O .NET gerará uma chamada para:

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

Isso manipula IXmlSerializable corretamente. Alguém sabe por que o .NET não usa essa função no caso polimórfico? Olhando para o C # que o serializador XML gera, parece-me que isso pode ser feito com bastante facilidade. Aqui está um código que recebi do serializador XML, com uma solução não testada:

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);
}

Isso é deixado de fora por razões técnicas ou apenas uma limitação de recurso? Recurso não suportado ou minha idiotice? Minhas habilidades interpessoas do Google me falham.

Eu encontrei algumas questões relacionadas aqui, com " C # Xml- Serializando uma classe derivada usando IXmlSerializable "sendo mais relevante. Isso me leva a acreditar que simplesmente não é possível.

Nesse caso, meu pensamento atual é injetar uma implementação IXmlSerializable padrão na classe base raiz. Então tudo será um IXmlSerializable e o .NET não irá reclamar. Eu posso usar o Reflection.Emit para extrair os corpos ReadXml e WriteXml para cada tipo de concreto, gerando XML que seria o mesmo que se eu usasse a biblioteca.

Algumas pessoas, quando confrontadas com um problema de serialização XML, pensam "Eu sei, vou usar Reflection.Emit para gerar código". Agora eles têm dois problemas.

P.S. Nota; Estou ciente das alternativas à serialização XML do .NET e sei que ele tem limitações. Eu também sei que salvar um POCO é muito mais simples do que lidar com tipos de dados abstratos. Mas eu tenho uma pilha de código legado e preciso de suporte para esquemas XML existentes.

Portanto, agradeço as respostas que mostram como isso é fácil em SomeOtherXML , YAML , XAML , ProtocolBuffers , DataContract , RandomJsonLibrary , Thrift ou sua biblioteca MorseCodeBasedSerializeToMp3 - hey eu poderia aprender alguma coisa -, o que eu estou esperando é um serializador XML para contornar, se não a solução.

    
por Jaap Suter 10.03.2009 в 20:18
fonte

3 respostas

2

Consegui reproduzir seu problema ao usar object :

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

No entanto, criei uma classe derivada de CustomSerializable :

public class CustomSerializableDerived : CustomSerializable
{
}

E tentou serializar:

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

Isso funcionou.

Assim, parece que o problema é restrito ao caso em que você especifica "objeto" como o tipo a ser serializado, mas não se você especificar um tipo de base concreto.

Vou fazer mais algumas pesquisas sobre isso de manhã.

    
por John Saunders 28.07.2009 / 05:18
fonte
1

Primeira de todas as declarações de posters

  

A serialização XML no .NET permite objetos polimórficos através do parâmetro extraTypes [] do construtor XmlSerializer.

é enganoso. De acordo com os extratos do MSDN é usado para:

  

Se um campo propriedade ou retornar uma matriz , o parâmetro extraTypes especifica objetos que podem ser inseridos na matriz.

Isso significa que, se em algum lugar em seu objeto gráfico serializado, objetos polimórficos forem retornados via array, eles poderão ser processados.

Embora eu não tenha realmente encontrado uma solução para serializar um tipo polimórfico como objeto XML raiz, fui capaz de serializar tipos polimórficos encontrados no gráfico de objetos usando serializador XML padrão ou IXmlSerializable. Veja abaixo a minha solução:

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);
    }
  }
}

Observe que usar

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

ou

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

sem XmlElement definido, faria com que o código falhasse na serialização.

    
por Rok 14.05.2011 / 01:21
fonte
0

Para que o IxmlSerializable funcione, a classe precisa ter um construtor nulo.

Considere uma classe base

  • MyBaseXmlClass: IXmlSerializable
    - implementar GetSchema
  • MyXmlClass: MyBaseXmlClass
    - ReadXml
    • WriteXml
por david valentine 29.07.2009 / 03:09
fonte