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.
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
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