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