.NET XML-сериализация gotchas? - программирование

.NET XML-сериализация gotchas?

Я столкнулся с несколькими ошибками при выполнении сериализации С# XML что я думал, что буду делиться:

  • Вы не можете сериализовать элементы, доступные только для чтения (например, KeyValuePairs)
  • Вы не можете сериализовать общий словарь. Вместо этого попробуйте этот класс оболочки (от http://weblogs.asp.net/pwelter34/archive/2006/05/03/444961.aspx):

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

Любая другая XML-сериализация прошла там?

4b9b3361

Ответ 1

Еще одна огромная проблема: при выводе XML через веб-страницу (ASP.NET) вы не хотите включать Unicode Byte-Order Mark. Конечно, способы использовать или не использовать спецификацию почти одинаковы:

BAD (включает спецификацию):

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

ХОРОШО:

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

Вы можете явно передать ложь, чтобы указать, что вы не хотите спецификацию. Обратите внимание на ясную, очевидную разницу между Encoding.UTF8 и UTF8Encoding.

Три дополнительных байта BOM в начале - (0xEFBBBF) или (239 187 191).

Ссылка: http://chrislaco.com/blog/troubleshooting-common-problems-with-the-xmlserializer/

Ответ 2

Я пока не могу комментировать, поэтому буду комментировать сообщение Dr8k и сделать еще одно замечание. Частные переменные, которые отображаются как общедоступные свойства getter/setter, и становятся сериализованными/десериализованными как таковые через эти свойства. Мы сделали это на моей старой работе вовремя.

Следует отметить, однако, что если у вас есть какая-либо логика в этих свойствах, логика запускается, поэтому иногда порядок сериализации действительно имеет значение. Члены неявно упорядочиваются тем, как они упорядочены в коде, но нет никаких гарантий, особенно когда вы наследуете другой объект. Явно заказываю их - это боль в тылу.

В прошлом я был сожжен этим.

Ответ 3

При сериализации в строку XML из потока памяти обязательно используйте MemoryStream # ToArray() вместо MemoryStream # GetBuffer(), или вы получите ненужные символы, которые не будут десериализоваться должным образом (из-за дополнительного буфера выделено).

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

Ответ 4

IEnumerables<T>, которые генерируются с возвратом доходности, не являются сериализуемыми. Это связано с тем, что компилятор генерирует отдельный класс для реализации yield return и этот класс не помечен как сериализуемый.

Ответ 5

Если сериализатор встречает элемент/свойство, у которого есть интерфейс как его тип, он не будет сериализоваться. Например, следующее не будет сериализоваться в XML:

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

Хотя это будет сериализоваться:

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

Ответ 6

Вы не можете сериализовать свойства только для чтения. У вас должен быть геттер и сеттер, даже если вы никогда не собираетесь использовать десериализацию, чтобы превратить XML в объект.

По той же причине вы не можете сериализовать свойства, возвращающие интерфейсы: десериализатор не знает, какой конкретный класс должен быть создан.

Ответ 7

О, вот хороший: поскольку код сериализации XML генерируется и помещается в отдельную DLL, вы не получаете никакой значимой ошибки, если в коде есть ошибка, которая разбивает сериализатор. Просто что-то вроде "невозможно найти s3d3fsdf.dll". Ницца.

Ответ 8

Еще одно замечание: вы не можете сериализовать частные/защищенные члены класса, если вы используете стандартную сериализацию XML.

Но вы можете указать настраиваемую логику сериализации XML, реализующую IXmlSerializable в вашем классе и сериализовать любые частные поля, которые вам нужны/нужны.

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

Ответ 9

Невозможно сериализовать объект, у которого нет безпараметрического конструктора (просто его укусил).

И по какой-то причине из следующих свойств значение становится сериализованным, но не FullName:

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

Я никогда не разбирался в том, почему, я просто изменил значение на внутреннее...

Ответ 10

Если созданная сборка XML Serialization не находится в том же контексте загрузки, что и код, пытающийся ее использовать, вы столкнетесь с такими ужасными ошибками, как:

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)

Причиной этого для меня был плагин, загруженный с помощью контекст LoadFrom, который имеет много недостатков в использовании контекст загрузки. Довольно весело отслеживать, что один вниз.

Ответ 13

Если вы попытаетесь сериализовать массив, List<T> или IEnumerable<T>, который содержит экземпляры подклассов T, вам нужно использовать XmlArrayItemAttribute, чтобы указать все используемые подтипы. В противном случае во время выполнения вы получите бесполезный System.InvalidOperationException при сериализации.

Вот часть полного примера из документации

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;

Ответ 14

Частные переменные/свойства не сериализуются в стандартном механизме сериализации XML, но находятся в двоичной сериализации.

Ответ 15

Свойства, помеченные атрибутом Obsolete, не сериализованы. Я не тестировал атрибут Deprecated, но я предполагаю, что он будет действовать одинаково.

Ответ 16

Я не могу это объяснить, но я обнаружил, что это не будет сериализоваться:

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

но это будет:

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

И также стоит отметить, что если вы сериализуете мейнстрим, вы можете захотеть найти 0, прежде чем использовать его.

Ответ 17

Если ваш XSD использует группы замещения, возможно, вы не можете (de) сериализовать его автоматически. Вам нужно будет написать свои собственные сериализаторы, чтобы справиться с этим сценарием.

Eg.

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

В этом примере конверт может содержать сообщения. Однако сериализатор по умолчанию .NET не различает Message, ExampleMessageA и ExampleMessageB. Он будет сериализоваться только в базовом классе сообщений и из него.

Ответ 18

Будьте осторожны с сериализацией без явной сериализации, это может привести к задержкам, в то время как .Net создает их. Я обнаружил это недавно при сериализации RSAParameters.

Ответ 19

Частные переменные/свойства не сериализован в сериализации XML, но находятся в двоичной сериализации.

Я считаю, что это также приведет вас к тому, что вы публикуете частные члены через общедоступные свойства - частные члены не получают сериализацию, поэтому публичные члены ссылаются на нулевые значения.