Проблемы с сериализацией .NET XML? [закрыто]

121

При сериализации C # 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?

kurious
источник
Ищите больше ошибок, лол, вы могли бы мне помочь: stackoverflow.com/questions/2663836/…
Шимми Вайцхандлер,
1
Кроме того, вы захотите взглянуть на реализацию сериализуемого словаря Чарльза Федуке, он заставил писателя xml замечать между атрибутируемыми членами и обычными членами, которые должны быть сериализованы сериализатором по умолчанию: deploymentzone.com/2008/09/19/…
Shimmy Weitzhandler
Не похоже, что он полностью улавливает все подводные камни. Я устанавливаю IEqualityComparer в конструкторе, но он не сериализуется в этом коде. Есть идеи, как расширить этот Словарь, чтобы включить в него эту информацию? можно ли обрабатывать эту информацию через объект Type?
ColinCren,

Ответы:

27

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

ПЛОХО (включая спецификацию):

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

ХОРОШО:

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

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

Три дополнительных байта спецификации в начале: (0xEFBBBF) или (239 187 191).

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

Kalid
источник
4
Ваш комментарий был бы еще более полезным, если бы вы сказали нам не просто что, а почему.
Нил,
1
На самом деле это не связано с сериализацией XML ... это просто проблема XmlTextWriter
Томас Левеск,
7
-1: Не имеет отношения к вопросу, и вы не должны использовать XmlTextWriter.NET 2.0 или выше.
Джон Сондерс
Очень полезная справочная ссылка. Спасибо.
Anil Vangari
21

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

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

Я был обожжен этим в прошлом.

Чарльз Грэм
источник
17
Я нашел этот пост, когда искал способы явно установить порядок полей. Это делается с помощью атрибутов: [XmlElementAttribute (Order = 1)] public int Field {...} Обратная сторона: атрибут должен быть указан для ВСЕХ полей в классе и всех его потомков! IMO Вы должны добавить это в свой пост.
Кристиан Диаконеску,
15

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

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

реал.
источник
3
прямо из документации "Обратите внимание, что буфер содержит выделенные байты, которые могут быть неиспользованными. Например, если строка" test "записана в объект MemoryStream, длина буфера, возвращаемого из GetBuffer, составляет 256, а не 4, с 252 байтами не используется. Чтобы получить только данные в буфере, используйте метод ToArray; однако ToArray создает копию данных в памяти ». msdn.microsoft.com/en-us/library/…
realgt
только сейчас это увидел. Больше не похоже на чушь.
Джон Сондерс
Никогда раньше не слышал об этом, что помогает при отладке.
Рики
10

Если сериализатор встречает член / свойство, тип которого имеет интерфейс, он не будет сериализован. Например, следующее не будет сериализовано в 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; }
}
Аллон Гуралнек
источник
Если вы получаете исключение с сообщением «Тип не разрешен для участника ...», возможно, это происходит.
Кайл Крулл
9

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

abatishchev
источник
Это относится к «другой» сериализации, то есть к атрибуту [Serializable]. Однако это не работает и для XmlSerializer.
Тим Робинсон,
8

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

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

Тим Робинсон
источник
1
На самом деле вы можете сериализовать свойство коллекции, даже если у него нет установщика, но оно должно быть инициализировано в конструкторе, чтобы десериализация могла добавлять к нему элементы,
Томас Левеск
7

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

Эрик Зи Берд
источник
11
Вы можете сгенерировать эту DLL заранее, используя XML «Инструмент создания сериализатора (Sgen.exe)» и развернуть вместе с вашим приложением.
huseyint
6

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

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

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

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

Benjol
источник
4
Конструктор без параметров может быть закрытым / защищенным. Для сериализатора XML этого будет достаточно. Проблема с FullName действительно странная, не должно происходить ...
Макс Галкин
@Yacoder: Может быть, не потому, double?а просто double?
Абатищев
FullName, вероятно, не nullбудет генерировать XML при сериализации
Джеспер
5

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

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

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

Макс Галкин
источник
4

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

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, который имеет много недостатков в использовании контекста Load. Довольно забавно отслеживать это.

user7116
источник
4

См. « Поддержка привязки атрибутов расширенного языка определения схемы XML » для получения подробной информации о том, что поддерживается XML-сериализатором, а также для получения подробной информации о том, как поддерживаются поддерживаемые функции XSD.

Джон Сондерс
источник
4

Если вы попытаетесь сериализовать массив, 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;
MarkJ
источник
3

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

Чарльз Грэм
источник
2
Да, если вы используете сериализацию XML "по умолчанию". Вы можете указать собственную логику сериализации XML, реализующую IXmlSerializable в своем классе, и сериализовать любые частные поля, которые вам нужны / нужны.
Макс Галкин
1
Что ж, это правда. Я это отредактирую. Но реализация этого интерфейса - заноза в заднице, насколько я помню.
Чарльз Грэм,
3

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

Джеймс Халс
источник
2

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

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

но это будет:

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

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

annakata
источник
Я думаю, это потому, что он не может его восстановить. Во втором примере он может вызывать item.Add () для добавления элементов в список. Он не может этого сделать с первого раза.
ilitirit
18
Используйте: [XmlArray ("item"), XmlArrayItem ("myClass", typeof (myClass))]
RvdK
1
ура за это! учиться чему-то каждый день
annakata
2

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

Кит
источник
2

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

Например.

<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. Он будет сериализоваться только в базовый класс Message и обратно.

ilitirit
источник
0

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

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

Dr8k
источник
Это неправда. Будет вызываться установщик публичного свойства, который, предположительно, установит частный член.
Джон Сондерс