Ho incontrato alcuni trucchi durante la serializzazione XML C # che pensavo di condividere:
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Serialization;
[XmlRoot("dictionary")]
public class SerializableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IXmlSerializable
{
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));
bool wasEmpty = reader.IsEmptyElement;
reader.Read();
if (wasEmpty)
return;
while (reader.NodeType != System.Xml.XmlNodeType.EndElement)
{
reader.ReadStartElement("item");
reader.ReadStartElement("key");
TKey key = (TKey)keySerializer.Deserialize(reader);
reader.ReadEndElement();
reader.ReadStartElement("value");
TValue value = (TValue)valueSerializer.Deserialize(reader);
reader.ReadEndElement();
this.Add(key, value);
reader.ReadEndElement();
reader.MoveToContent();
}
reader.ReadEndElement();
}
public void WriteXml(System.Xml.XmlWriter writer)
{
XmlSerializer keySerializer = new XmlSerializer(typeof(TKey));
XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue));
foreach (TKey key in this.Keys)
{
writer.WriteStartElement("item");
writer.WriteStartElement("key");
keySerializer.Serialize(writer, key);
writer.WriteEndElement();
writer.WriteStartElement("value");
TValue value = this[key];
valueSerializer.Serialize(writer, value);
writer.WriteEndElement();
writer.WriteEndElement();
}
}
}
Qualche altra soluzione per la serializzazione XML?
Un altro enorme gotcha: quando si emette XML attraverso una pagina Web (ASP.NET), non si desidera includere il nicode Byte-Order Mark . Ovviamente, i modi di utilizzare o meno la DBA sono quasi gli stessi:
XmlTextWriter wr = new XmlTextWriter(stream, new System.Text.Encoding.UTF8);
XmlTextWriter wr = new XmlTextWriter(stream, new System.Text.UTF8Encoding(false))
È possibile passare esplicitamente false per indicare che non si desidera la distinta base. Notare la chiara, ovvia differenza tra Encoding.UTF8
e UTF8Encoding
.
I tre byte DBA aggiuntivi all'inizio sono (0xEFBBBF) o (239 187 191).
Riferimento: http://chrislaco.com/blog/tro troubleshooting-common-problems-with-the-xmlserializer/
Non posso ancora commentare, quindi commenterò il post di Dr8k e farò un'altra osservazione. Variabili private che sono esposte come proprietà getter/setter pubbliche e vengono serializzate/deserializzate come tali attraverso tali proprietà. L'abbiamo fatto al mio vecchio lavoro in quel momento.
Una cosa da notare però è che se si dispone di una logica in tali proprietà, la logica viene eseguita, quindi a volte l'ordine di serializzazione conta davvero. I membri sono implicitamente ordinati in base a come sono ordinati nel codice, ma non ci sono garanzie, specialmente quando si eredita un altro oggetto. Ordinarli esplicitamente è un dolore nella parte posteriore.
Sono stato bruciato da questo in passato.
Quando si serializza in una stringa XML da un flusso di memoria, assicurarsi di utilizzare MemoryStream # ToArray () invece di MemoryStream # GetBuffer () o si finiranno con caratteri spazzatura che non si deserializzeranno correttamente (a causa del buffer aggiuntivo allocato).
http://msdn.Microsoft.com/en-us/library/system.io.memorystream.getbuffer (VS.80) aspx
IEnumerables<T>
che vengono generati tramite i rendimenti non sono serializzabili. Questo perché il compilatore genera una classe separata per implementare il rendimento e tale classe non è contrassegnata come serializzabile.
Se il serializzatore incontra un membro/proprietà che ha un'interfaccia come tipo, non si serializzerà. Ad esempio, quanto segue non verrà serializzato in XML:
public class ValuePair
{
public ICompareable Value1 { get; set; }
public ICompareable Value2 { get; set; }
}
Anche se questo serializzerà:
public class ValuePair
{
public object Value1 { get; set; }
public object Value2 { get; set; }
}
Non è possibile serializzare le proprietà di sola lettura. È necessario disporre di un getter e un setter, anche se non si intende mai utilizzare la deserializzazione per trasformare XML in un oggetto.
Per lo stesso motivo, non è possibile serializzare le proprietà che restituiscono interfacce: il deserializzatore non saprebbe quale classe concreta creare un'istanza.
Oh, eccone una buona: poiché il codice di serializzazione XML viene generato e inserito in una DLL separata, non si ottiene alcun errore significativo quando si verifica un errore nel codice che interrompe il serializzatore. Solo qualcosa come "impossibile trovare s3d3fsdf.dll". Bello.
Impossibile serializzare un oggetto che non ha un costruttore senza parametri (appena morso da quello).
E per qualche motivo, dalle seguenti proprietà, Value viene serializzato, ma non FullName:
public string FullName { get; set; }
public double Value { get; set; }
Non ho mai avuto modo di capire perché, ho appena cambiato il valore in interno ...
Un'altra cosa da notare: non è possibile serializzare i membri della classe privata/protetta se si utilizza la serializzazione XML "predefinita".
Ma puoi specificare la logica di serializzazione XML personalizzata implementando IXmlSerializable nella tua classe e serializzare tutti i campi privati che desideri/desideri.
http://msdn.Microsoft.com/en-us/library/system.xml.serialization.ixmlserializable.aspx
Potrebbero verificarsi problemi durante la serializzazione di oggetti di tipo Colore e/o Carattere.
Ecco i consigli che mi hanno aiutato:
http://www.codeproject.com/KB/XML/xmlsettings.aspx
http://www.codeproject.com/KB/cs/GenericXmlSerializition.aspx
Vedere " Supporto di associazione degli attributi del linguaggio di definizione dello schema XML avanzato " per i dettagli su ciò che è supportato dal serializzatore XML e per i dettagli su come sono supportate le funzionalità XSD supportate.
Se si tenta di serializzare un array, List<T>
, o IEnumerable<T>
che contiene istanze di sottoclassi di T
, devi utilizzare XmlArrayItemAttribute per elencare tutti i sottotipi in uso. Altrimenti otterrai un inutile System.InvalidOperationException
in fase di esecuzione quando si serializza.
Ecco parte di un esempio completo tratto da la documentazione
public class Group
{
/* The XmlArrayItemAttribute allows the XmlSerializer to insert both the base
type (Employee) and derived type (Manager) into serialized arrays. */
[XmlArrayItem(typeof(Manager)), XmlArrayItem(typeof(Employee))]
public Employee[] Employees;
Se l'assembly generato dalla serializzazione XML non si trova nello stesso contesto di caricamento del codice che tenta di utilizzarlo, si verificheranno errori incredibili come:
System.InvalidOperationException: There was an error generating the XML document.
---System.InvalidCastException: Unable to cast object
of type 'MyNamespace.Settings' to type 'MyNamespace.Settings'. at
Microsoft.Xml.Serialization.GeneratedAssembly.
XmlSerializationWriterSettings.Write3_Settings(Object o)
La causa di ciò è stata per me un plugin caricato usando LoadFrom context che ha molti svantaggi nell'uso del contesto Load. Abbastanza divertente rintracciarlo.
Le proprietà contrassegnate con l'attributo Obsolete
non sono serializzate. Non ho testato con l'attributo Deprecated
ma suppongo che si comporterebbe allo stesso modo.
Le variabili/proprietà private non sono serializzate nel meccanismo predefinito per la serializzazione XML, ma sono in serializzazione binaria.
Se l'XSD utilizza gruppi di sostituzione, è probabile che non sia possibile (de) serializzarlo automaticamente. Dovrai scrivere i tuoi serializzatori per gestire questo scenario.
Per esempio.
<xs:complexType name="MessageType" abstract="true">
<xs:attributeGroup ref="commonMessageAttributes"/>
</xs:complexType>
<xs:element name="Message" type="MessageType"/>
<xs:element name="Envelope">
<xs:complexType mixed="false">
<xs:complexContent mixed="false">
<xs:element ref="Message" minOccurs="0" maxOccurs="unbounded"/>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="ExampleMessageA" substitutionGroup="Message">
<xs:complexType mixed="false">
<xs:complexContent mixed="false">
<xs:attribute name="messageCode"/>
</xs:complexContent>
</xs:complexType>
</xs:element>
<xs:element name="ExampleMessageB" substitutionGroup="Message">
<xs:complexType mixed="false">
<xs:complexContent mixed="false">
<xs:attribute name="messageCode"/>
</xs:complexContent>
</xs:complexType>
</xs:element>
In questo esempio, una busta può contenere messaggi. Tuttavia, il serializzatore predefinito di .NET non distingue tra Message, ExampleMessageA e ExampleMessageB. Serializzerà solo verso e dalla classe Message di base.
Prestare attenzione alla serializzazione dei tipi senza serializzazione esplicita, può causare ritardi mentre .Net li costruisce. L'ho scoperto di recente durante la serializzazione di RSAParameters .
Non posso davvero spiegarlo, ma ho scoperto che questo non serializzerà:
[XmlElement("item")]
public myClass[] item
{
get { return this.privateList.ToArray(); }
}
ma questo:
[XmlElement("item")]
public List<myClass> item
{
get { return this.privateList; }
}
E vale anche la pena notare che se si sta eseguendo la serializzazione su un memstream, è possibile cercare 0 prima di utilizzarlo.
Le variabili/proprietà private non sono serializzate nella serializzazione XML, ma sono nella serializzazione binaria.
Credo che questo ti renda anche se stai esponendo i membri privati attraverso proprietà pubbliche - i membri privati non vengono serializzati, quindi i membri pubblici fanno tutti riferimento a valori null.