it-swarm.it

Gotcha di serializzazione XML .NET?

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?

120
Kalid

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:

BAD (include BOM):

XmlTextWriter wr = new XmlTextWriter(stream, new System.Text.Encoding.UTF8);

BENE:

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/

27
Kalid

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.

21
Charles Graham

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

15
realgt

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.

10
bwillard

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; }
}
10
Allon Guralnek

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.

8
Tim Robinson

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.

7
Eric Z Beard

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

6
Benjol

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

5
Max Galkin

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

4
Max Galkin

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.

4
John Saunders

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;
4
MarkJ

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.

4
user7116

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.

3
James Hulse

Le variabili/proprietà private non sono serializzate nel meccanismo predefinito per la serializzazione XML, ma sono in serializzazione binaria.

3
Charles Graham

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.

2
ilitirit

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 .

2
Keith

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.

2
annakata

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.

0
Dr8k