Fallstricke bei der .NET XML-Serialisierung? [geschlossen]

121

Bei der C # XML-Serialisierung sind mir einige Fallstricke begegnet, von denen ich dachte, ich würde sie teilen:


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

Gibt es noch andere Fallstricke bei der XML-Serialisierung?

kurious
quelle
Suchen Sie nach weiteren Fallstricken lol, vielleicht können Sie mir helfen: stackoverflow.com/questions/2663836/…
Shimmy Weitzhandler
1
Außerdem möchten Sie einen Blick auf Charles Fedukes Implementierung des serialzable-Wörterbuchs werfen. Er hat den XML-Writer dazu gebracht, zwischen zuordenbaren Mitgliedern und regulären Mitgliedern zu bemerken, die vom Standard-Serializer serialisiert werden sollen: deployzone.com/2008/09/19/…
Shimmy Weitzhandler
Dies scheint nicht alle Fallstricke zu erfassen. Ich setze den IEqualityComparer im Konstruktor, aber das wird in diesem Code nicht serialisiert. Haben Sie Ideen, wie Sie dieses Wörterbuch um diese Informationen erweitern können? Könnten diese Informationen über das Type-Objekt verarbeitet werden?
ColinCren

Antworten:

27

Ein weiteres großes Problem: Wenn Sie XML über eine Webseite (ASP.NET) ausgeben, möchten Sie das Unicode-Byte-Order-Zeichen nicht einfügen . Natürlich sind die Möglichkeiten, die Stückliste zu verwenden oder nicht zu verwenden, fast gleich:

BAD (einschließlich Stückliste):

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

GUT:

XmlTextWriter  wr = new XmlTextWriter(stream, new System.Text.UTF8Encoding(false))

Sie können explizit false übergeben, um anzugeben, dass Sie die Stückliste nicht möchten. Beachten Sie den klaren, offensichtlichen Unterschied zwischen Encoding.UTF8und UTF8Encoding.

Die drei zusätzlichen Stücklistenbytes am Anfang sind (0xEFBBBF) oder (239 187 191).

Referenz: http://chrislaco.com/blog/troubleshooter-common-problems-with-the-xmlserializer/

Kalid
quelle
4
Ihr Kommentar wäre noch nützlicher, wenn Sie uns nicht nur sagen würden, was, sondern warum.
Neil
1
Dies hat nicht wirklich mit XML-Serialisierung zu tun ... es ist nur ein XmlTextWriter-Problem
Thomas Levesque
7
-1: Nicht im Zusammenhang mit der Frage, und Sie sollten nicht XmlTextWriterin .NET 2.0 oder höher verwenden.
John Saunders
Sehr hilfreicher Referenzlink. Vielen Dank.
Anil Vangari
21

Ich kann noch keine Kommentare abgeben, daher werde ich den Beitrag von Dr8k kommentieren und eine weitere Bemerkung machen. Private Variablen, die als öffentliche Getter- / Setter-Eigenschaften verfügbar gemacht werden und über diese Eigenschaften als solche serialisiert / deserialisiert werden. Wir haben es die ganze Zeit bei meinem alten Job gemacht.

Eine Sache zu beachten ist jedoch, dass, wenn Sie eine Logik in diesen Eigenschaften haben, die Logik ausgeführt wird, so dass manchmal die Reihenfolge der Serialisierung tatsächlich wichtig ist. Die Mitglieder sind implizit nach der Reihenfolge im Code geordnet, es gibt jedoch keine Garantien, insbesondere wenn Sie ein anderes Objekt erben. Sie explizit zu bestellen ist ein Schmerz im Heck.

Das hat mich in der Vergangenheit verbrannt.

Charles Graham
quelle
17
Ich habe diesen Beitrag gefunden, als ich nach Möglichkeiten gesucht habe, die Reihenfolge der Felder explizit festzulegen. Dies geschieht mit folgenden Attributen: [XmlElementAttribute (Order = 1)] public int Field {...} Nachteil: Das Attribut muss für ALLE Felder in der Klasse und alle ihre Nachkommen angegeben werden! IMO Sie sollten dies zu Ihrem Beitrag hinzufügen.
Cristian Diaconescu
15

Stellen Sie beim Serialisieren in eine XML-Zeichenfolge aus einem Speicherstrom sicher, dass Sie MemoryStream # ToArray () anstelle von MemoryStream # GetBuffer () verwenden. Andernfalls werden Junk-Zeichen angezeigt, die nicht ordnungsgemäß deserialisiert werden (aufgrund des zugewiesenen zusätzlichen Puffers).

http://msdn.microsoft.com/en-us/library/system.io.memorystream.getbuffer(VS.80).aspx

realgt
quelle
3
direkt aus den Dokumenten "Beachten Sie, dass der Puffer zugewiesene Bytes enthält, die möglicherweise nicht verwendet werden. Wenn beispielsweise die Zeichenfolge" test "in das MemoryStream-Objekt geschrieben wird, beträgt die Länge des von GetBuffer zurückgegebenen Puffers 256 und nicht 4 mit 252 Bytes unbenutzt. Um nur die Daten im Puffer abzurufen, verwenden Sie die ToArray-Methode. ToArray erstellt jedoch eine Kopie der Daten im Speicher. " msdn.microsoft.com/en-us/library/…
realgt
erst jetzt sah das. Klingt nicht mehr nach Unsinn.
John Saunders
Ich habe das noch nie gehört, was beim Debuggen hilfreich ist.
Ricky
10

Wenn der Serializer auf ein Mitglied / eine Eigenschaft stößt, deren Typ eine Schnittstelle hat, wird er nicht serialisiert. Beispielsweise wird Folgendes nicht in XML serialisiert:

public class ValuePair
{
    public ICompareable Value1 { get; set; }
    public ICompareable Value2 { get; set; }
}

Dies wird jedoch serialisiert:

public class ValuePair
{
    public object Value1 { get; set; }
    public object Value2 { get; set; }
}
Allon Guralnek
quelle
Wenn Sie eine Ausnahme mit der Meldung "Typ für Mitglied nicht aufgelöst ..." erhalten, passiert möglicherweise Folgendes.
Kyle Krull
9

IEnumerables<T>die über Renditerenditen generiert werden, sind nicht serialisierbar. Dies liegt daran, dass der Compiler eine separate Klasse generiert, um die Ertragsrückgabe zu implementieren, und diese Klasse nicht als serialisierbar markiert ist.

abatishchev
quelle
Dies gilt für die 'andere' Serialisierung, dh das Attribut [Serializable]. Dies funktioniert jedoch auch nicht für XmlSerializer.
Tim Robinson
8

Sie können schreibgeschützte Eigenschaften nicht serialisieren. Sie müssen einen Getter und einen Setter haben, auch wenn Sie niemals die Deserialisierung verwenden möchten, um XML in ein Objekt umzuwandeln.

Aus dem gleichen Grund können Sie keine Eigenschaften serialisieren, die Schnittstellen zurückgeben: Der Deserializer weiß nicht, welche konkrete Klasse instanziiert werden soll.

Tim Robinson
quelle
1
Eigentlich können Sie eine Sammlungseigenschaft serialisieren, auch wenn sie keinen Setter hat, aber sie muss im Konstruktor initialisiert werden, damit die Deserialisierung ihr Elemente hinzufügen kann
Thomas Levesque
7

Oh, hier ist eine gute: Da der XML-Serialisierungscode generiert und in einer separaten DLL abgelegt wird, erhalten Sie keinen aussagekräftigen Fehler, wenn in Ihrem Code ein Fehler vorliegt, der den Serializer beschädigt. Nur so etwas wie "s3d3fsdf.dll kann nicht gefunden werden". Nett.

Eric Z Bart
quelle
11
Sie können diese DLL vorab mithilfe des XML "Serializer Generator Tool (Sgen.exe)" generieren und mit Ihrer Anwendung bereitstellen.
Huseyint
6

Ein Objekt, das keinen parameterlosen Construtor hat, kann nicht serialisiert werden (wurde gerade von diesem gebissen).

Aus irgendeinem Grund wird Value aus den folgenden Eigenschaften serialisiert, nicht jedoch FullName:

    public string FullName { get; set; }
    public double Value { get; set; }

Ich habe nie herausgefunden, warum, ich habe nur Value in intern geändert ...

Benjol
quelle
4
Der parameterlose Konstruktor kann privat / geschützt sein. Es wird für XML Serializer ausreichen. Das Problem mit FullName ist wirklich seltsam, sollte nicht passieren ...
Max Galkin
@Yacoder: Vielleicht weil nicht double?aber nur double?
Abatishchev
FullName war wahrscheinlich nullund wird daher kein XML generieren, wenn es serialisiert wird
Jesper
5

Noch etwas zu beachten: Sie können keine privaten / geschützten Klassenmitglieder serialisieren, wenn Sie die "Standard" XML-Serialisierung verwenden.

Sie können jedoch eine benutzerdefinierte XML-Serialisierungslogik angeben, die IXmlSerializable in Ihrer Klasse implementiert, und alle privaten Felder, die Sie benötigen / möchten, serialisieren.

http://msdn.microsoft.com/en-us/library/system.xml.serialization.ixmlserializable.aspx

Max Galkin
quelle
4

Wenn sich Ihre von der XML-Serialisierung generierte Assembly nicht im selben Ladekontext befindet wie der Code, der versucht, sie zu verwenden, treten tolle Fehler auf:

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)

Die Ursache dafür war für mich ein Plugin, das im LoadFrom-Kontext geladen wurde und viele Nachteile bei der Verwendung des Load-Kontexts hat. Ziemlich viel Spaß beim Aufspüren.

user7116
quelle
4

Wenn Sie versuchen, ein Array zu serialisieren List<T>, oder IEnumerable<T>das Instanzen von Unterklassen von enthält T, müssen Sie das XmlArrayItemAttribute verwenden, um alle verwendeten Untertypen aufzulisten . Andernfalls erhalten Sie System.InvalidOperationExceptionzur Laufzeit beim Serialisieren eine nicht hilfreiche Datei.

Hier ist ein Teil eines vollständigen Beispiels aus der Dokumentation

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;
MarkJ
quelle
3

Private Variablen / Eigenschaften werden im Standardmechanismus für die XML-Serialisierung nicht serialisiert, sondern in der binären Serialisierung.

Charles Graham
quelle
2
Ja, wenn Sie die "Standard" XML-Serialisierung verwenden. Sie können eine benutzerdefinierte XML-Serialisierungslogik angeben, die IXmlSerializable in Ihrer Klasse implementiert, und alle privaten Felder serialisieren, die Sie benötigen / möchten.
Max Galkin
1
Nun, das ist wahr. Ich werde das bearbeiten. Aber die Implementierung dieser Schnittstelle ist, soweit ich mich erinnere, eine Art Schmerz im Arsch.
Charles Graham
3

Mit dem ObsoleteAttribut gekennzeichnete Eigenschaften werden nicht serialisiert. Ich habe nicht mit DeprecatedAttribut getestet , aber ich gehe davon aus, dass es genauso funktionieren würde.

James Hulse
quelle
2

Ich kann das nicht wirklich erklären, aber ich habe festgestellt, dass es nicht serialisiert wird:

[XmlElement("item")]
public myClass[] item
{
    get { return this.privateList.ToArray(); }
}

aber das wird:

[XmlElement("item")]
public List<myClass> item
{
    get { return this.privateList; }
}

Beachten Sie auch, dass Sie bei der Serialisierung eines Memstreams möglicherweise vor der Verwendung auf 0 suchen möchten, bevor Sie ihn verwenden.

Annakata
quelle
Ich denke, das liegt daran, dass es nicht wieder aufgebaut werden kann. Im zweiten Beispiel kann item.Add () aufgerufen werden, um Elemente zur Liste hinzuzufügen. Es kann es nicht im ersten tun.
ilitirit
18
Verwendung: [XmlArray ("item"), XmlArrayItem ("myClass", typeof (myClass))]
RvdK
1
Prost dafür! lerne jeden Tag etwas
Annakata
2

Wenn Ihre XSD Substitutionsgruppen verwendet, können Sie sie wahrscheinlich nicht automatisch (de) serialisieren. Sie müssen Ihre eigenen Serializer schreiben, um dieses Szenario zu handhaben.

Z.B.

<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 diesem Beispiel kann ein Umschlag Nachrichten enthalten. Der Standard-Serializer von .NET unterscheidet jedoch nicht zwischen Message, ExampleMessageA und ExampleMessageB. Es wird nur zur und von der Basisnachrichtenklasse serialisiert.

ilitirit
quelle
0

Private Variablen / Eigenschaften werden bei der XML-Serialisierung nicht serialisiert, sondern bei der binären Serialisierung.

Ich glaube, das bringt Sie auch, wenn Sie die privaten Mitglieder über öffentliche Eigenschaften verfügbar machen - die privaten Mitglieder werden nicht serialisiert, sodass alle öffentlichen Mitglieder auf Nullwerte verweisen.

Dr8k
quelle
Das ist nicht wahr. Der Setter des öffentlichen Eigentums würde gerufen und würde vermutlich das private Mitglied setzen.
John Saunders