2009-03-10 25 views
9

La serializzazione XML in .NET consente oggetti polimorfici tramite il parametro extraTypes[] del costruttore XmlSerializer. Consente inoltre la personalizzazione della serializzazione XML per i tipi che implementano IXmlSerializable.Tipi polimorfici e IXmlSerializable

Tuttavia, sono in grado di combinare queste due caratteristiche - come dimostrato in questo esempio minimo:

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

L'ultima riga lanci System.InvalidOperationException con questo messaggio:

Il tipo CsFoo.CustomSerializable non può essere utilizzato in questo contesto per utilizzare CsFoo.CustomSerializable come parametro, restituire il tipo o membro di una classe o di una struttura, il parametro, restituire il tipo o il membro deve essere dichiarato come ty pe CsFoo.CustomSerializable (non può essere oggetto). Gli oggetti di tipo CsFoo.CustomSerializable non possono essere utilizzati in raccolte non tipizzate, come ArrayLists.

guado attraverso le assemblee XML generati in modo dinamico, in ultima analisi, siamo tornati al codice di libreria standard NET chiamando:

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

A sua volta, questo porta a:

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

    // Rest omitted... 

spettacoli Reflector che la risorsa XmlInvalidSerializable corrisponda alla stringa sopra, ad esempio, WriteTypedPrimitive non piace IXmlSerializable.

Se generiamo un serializzatore non polimorfica, in questo modo:

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

.NET genera una chiamata a:

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

Questo gestisce IXmlSerializable correttamente. Qualcuno sa perché .NET non usa questa funzione nel caso polimorfico? Guardando il C# che genera il serializzatore XML, mi sembra che questo possa essere fatto abbastanza facilmente. Ecco un po 'di codice che ho ricevuto dal serializzatore XML, con una soluzione testata:

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

È questo lasciato fuori per motivi tecnici o semplicemente una limitazione caratteristica? Funzionalità non supportata o mia idiozia? Le mie competenze su Google non mi riescono.

Ho trovato alcune domande correlate qui, con "C# Xml-Serializing a derived class using IXmlSerializable" che è più rilevante. Mi porta a credere che semplicemente non sia possibile.

In questo caso, il mio attuale pensiero è quello di iniettare un'implementazione predefinita nella classe di base root. Quindi tutto sarà un IXmlSerializable e .NET non si lamenterà. Posso usare Reflection.Emit per estrarre i corpi ReadXml e WriteXml per ogni tipo di calcestruzzo, generando XML che apparirebbe lo stesso come se utilizzassi quello della libreria.

Alcune persone, di fronte a un problema di serializzazione XML, pensano "Lo so, userò Reflection.Emit per generare codice." Ora hanno due problemi.


P.S. Nota; Sono a conoscenza di alternative alla serializzazione XML di .NET e so che ha delle limitazioni. So anche che salvare un POCO è molto più semplice che trattare con tipi di dati astratti. Ma ho una pila di codice legacy e ho bisogno di supporto per gli schemi XML esistenti.

Così, mentre mi rendo conto risposte che mostrano come questo è facile in SomeOtherXML, YAML, XAML, ProtocolBuffers, DataContract, RandomJsonLibrary, Thrift, o la libreria MorseCodeBasedSerializeToMp3 - ehi potrei imparare qualcosa -, quello che spero per è un Serializzazione XML work-around, se non soluzione.

+0

Anche se un'aggiunta abbastanza tarda, comunque: la soluzione con tipi aggiuntivi non funziona per me nel caso di IXmlSerializable. Tuttavia [questo approccio] (http://www.softwarerockstar.com/2006/12/using-ixmlserializable-to-overcome-not-expected-error-on-derived-classes/) funziona, in quanto consente alla classe deserializzata essere diverso dalla classe di proprietà (!), quindi qualsiasi logica personalizzata che trova il tipo giusto da creare può essere implementata in questo modo. – Vlad

+0

C'è un'altra soluzione dichiarata [qui] (https://connect.microsoft.com/VisualStudio/feedback/details/422577/incorrect-deserialization-of-polymorphic-type-that-implements-ixmlserializable) (usando l'attributo 'XmlSchemaProvider' per associare i tipi xml ai tipi .net), ma manca il codice e non sono riuscito a far funzionare l'idea. Chiunque? – Vlad

risposta

2

sono stato in grado di riprodurre il problema, quando si utilizza object:

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

Tuttavia, Poi ho creato una classe derivata di CustomSerializable:

public class CustomSerializableDerived : CustomSerializable 
{ 
} 

e ha cercato di serializzare esso:

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

Questo ha funzionato.

Quindi, sembrerebbe che il problema sia limitato al caso in cui si specifica "oggetto" come tipo da serializzare, ma non se si specifica un tipo di base concreto.

Farò qualche altra ricerca su questo al mattino.

0

Affinché IxmlSerializable funzioni, la classe deve avere un costruttore nullo.

consideri una classe base

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

-1: ha un costruttore predefinito; potresti usare il codice reale invece del solo testo per illustrare il tuo punto. –

1

Prima di tutto manifesti dichiarazione

La serializzazione XML in .NET permette di oggetti polimorfici attraverso le extraTypes [] parametro del costruttore XmlSerializer.

è fuorviante. Secondo MSDN extratypes è utilizzato per:

Se un o campo proprietà restituisce un matrice, il parametro extraTypes specifica oggetti che possono essere inseriti nella matrice.

Significa che se da qualche parte nel vostro oggetto serializzato gli oggetti polimorfici vengono restituiti tramite array, possono essere elaborati.

Anche se non ho trovato una soluzione per serializzare un tipo polimorfico come oggetto XML radice, sono stato in grado di serializzare i tipi polimorfici trovati nel grafico degli oggetti utilizzando serializer XML standard o IXmlSerializable. Vedi sotto per la mia soluzione:

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

Si noti che utilizzando

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

o

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

senza XmlElement definito, causerebbe il codice a fallire a serializzazione.

+0

Nota per i futuri lettori: se 'BarProperty' è qualcosa come un array' [XmlArrayItem (/ * ... * /)] 'deve essere usato al posto di' [XmlElement (/ * ... * /)] ' –

Problemi correlati