Modo corretto per implementare IXmlSerializable?

Una volta che un programmatore decide di implementare IXmlSerializable , quali sono le regole e le migliori pratiche per implementarlo? Ho sentito che GetSchema() dovrebbe restituire null e ReadXml dovrebbe passare all’elemento successivo prima di tornare. È vero? E che dire di WriteXml – dovrebbe scrivere un elemento radice per l’object o si suppone che la radice sia già stata scritta? Come devono essere trattati e scritti gli oggetti figli?

Ecco un esempio di ciò che ho ora. Lo aggiornerò man mano che avrò buone risposte.

 public class MyCalendar : IXmlSerializable { private string _name; private bool _enabled; private Color _color; private List _events = new List(); public XmlSchema GetSchema() { return null; } public void ReadXml(XmlReader reader) { if (reader.MoveToContent() == XmlNodeType.Element && reader.LocalName == "MyCalendar") { _name = reader["Name"]; _enabled = Boolean.Parse(reader["Enabled"]); _color = Color.FromArgb(Int32.Parse(reader["Color"])); if (reader.ReadToDescendant("MyEvent")) { while (reader.MoveToContent() == XmlNodeType.Element && reader.LocalName == "MyEvent") { MyEvent evt = new MyEvent(); evt.ReadXml(reader); _events.Add(evt); } } reader.Read(); } } public void WriteXml(XmlWriter writer) { writer.WriteAttributeString("Name", _name); writer.WriteAttributeString("Enabled", _enabled.ToString()); writer.WriteAttributeString("Color", _color.ToArgb().ToString()); foreach (MyEvent evt in _events) { writer.WriteStartElement("MyEvent"); evt.WriteXml(writer); writer.WriteEndElement(); } } } public class MyEvent : IXmlSerializable { private string _title; private DateTime _start; private DateTime _stop; public XmlSchema GetSchema() { return null; } public void ReadXml(XmlReader reader) { if (reader.MoveToContent() == XmlNodeType.Element && reader.LocalName == "MyEvent") { _title = reader["Title"]; _start = DateTime.FromBinary(Int64.Parse(reader["Start"])); _stop = DateTime.FromBinary(Int64.Parse(reader["Stop"])); reader.Read(); } } public void WriteXml(XmlWriter writer) { writer.WriteAttributeString("Title", _title); writer.WriteAttributeString("Start", _start.ToBinary().ToString()); writer.WriteAttributeString("Stop", _stop.ToBinary().ToString()); } } 

XML campione corrispondente

      

Sì, GetSchema () dovrebbe restituire null .

Metodo IXmlSerializable.GetSchema Questo metodo è riservato e non deve essere utilizzato. Quando si implementa l’interfaccia IXmlSerializable, è necessario restituire un riferimento null (Nothing in Visual Basic) da questo metodo e, invece, se è richiesto uno schema personalizzato, applicare XmlSchemaProviderAttribute alla class.

Sia per la lettura che per la scrittura, l’elemento object è già stato scritto, quindi non è necessario aggiungere un elemento esterno in scrittura. Ad esempio, puoi semplicemente iniziare a leggere / scrivere attributi nei due.

Per scrivere :

L’implementazione WriteXml fornita dovrebbe scrivere la rappresentazione XML dell’object. Il framework scrive un elemento wrapper e posiziona il writer XML dopo l’avvio. La tua implementazione può scrivere il suo contenuto, inclusi gli elementi figlio. Il framework chiude quindi l’elemento wrapper.

E per leggere :

Il metodo ReadXml deve ricostituire l’object utilizzando le informazioni che sono state scritte con il metodo WriteXml.

Quando viene chiamato questo metodo, il lettore viene posizionato all’inizio dell’elemento che racchiude le informazioni per il tuo tipo. Cioè, appena prima del tag di inizio che indica l’inizio di un object serializzato. Quando questo metodo ritorna, deve aver letto l’intero elemento dall’inizio alla fine, incluso tutto il suo contenuto. A differenza del metodo WriteXml, il framework non gestisce automaticamente l’elemento wrapper. La tua implementazione deve farlo. La mancata osservanza di queste regole di posizionamento può far sì che il codice generi eccezioni in tempo di esecuzione impreviste o dati corrotti.

Sono d’accordo che è un po ‘poco chiaro, ma si riduce a “è il tuo lavoro di Read() il tag di elemento finale del wrapper”.

Ho scritto un articolo sull’argomento con i campioni poiché la documentazione MSDN è ormai poco chiara e gli esempi che si possono trovare sul web vengono spesso implementati in modo non corretto.

Le insidie ​​sono la gestione di elementi locali e vuoti accanto a ciò che Marc Gravell ha già menzionato.

http://www.codeproject.com/KB/XML/ImplementIXmlSerializable.aspx

Sì, il tutto è un po ‘un campo minato, non è vero? La risposta di Marc Gravell copre quasi tutto, ma vorrei aggiungere che in un progetto su cui ho lavorato abbiamo trovato piuttosto scomodo dover scrivere manualmente l’elemento XML esterno. Ha inoltre generato nomi di elementi XML incoerenti per oggetti dello stesso tipo.

La nostra soluzione era di definire la nostra interfaccia IXmlSerializable , derivata da quella di sistema, che ha aggiunto un metodo chiamato WriteOuterXml() . Come puoi immaginare, questo metodo dovrebbe semplicemente scrivere l’elemento esterno, quindi chiamare WriteXml() , quindi scrivere la fine dell’elemento. Ovviamente, il serializzatore XML di sistema non chiamerebbe questo metodo, quindi è stato utile solo quando abbiamo effettuato la nostra serializzazione, in modo tale che potrebbe essere utile o meno nel tuo caso. Allo stesso modo, abbiamo aggiunto un metodo ReadContentXml() , che non ha letto l’elemento esterno, ma solo il suo contenuto.

Se hai già una rappresentazione XmlDocument della tua class o preferisci il modo XmlDocument di lavorare con le strutture XML, un modo rapido e sporco di implementare IXmlSerializable è semplicemente passare questo xmldoc alle varie funzioni.

ATTENZIONE: XmlDocument (e / o XDocument) è un ordine di grandezza più lento di xmlreader / writer, quindi se le prestazioni sono un requisito assoluto, questa soluzione non fa per te!

 class ExampleBaseClass : IXmlSerializable { public XmlDocument xmlDocument { get; set; } public XmlSchema GetSchema() { return null; } public void ReadXml(XmlReader reader) { xmlDocument.Load(reader); } public void WriteXml(XmlWriter writer) { xmlDocument.WriteTo(writer); } }