Проблемы с сериализацией .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?


Ищите больше ошибок, лол, вы могли бы мне помочь: 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/


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, и этот класс не помечен как сериализуемый.


Это относится к «другой» сериализации, то есть к атрибуту [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 на внутреннее ...


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. Довольно забавно отслеживать это.



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;

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, прежде чем использовать его.


Я думаю, это потому, что он не может его восстановить. Во втором примере он может вызывать item.Add () для добавления элементов в список. Он не может этого сделать с первого раза.
— ilitirit

18
Используйте: [XmlArray ("item"), XmlArrayItem ("myClass", typeof (myClass))]
— RvdK

1
ура за это! учиться чему-то каждый день
— annakata


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 и обратно.


0

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

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


Это неправда. Будет вызываться установщик публичного свойства, который, предположительно, установит частный член.
— Джон Сондерс
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.