После загрузки вашего проекта я смог создать что-то похожее на пример Minimal, Complete и Verifiable здесь: https://dotnetfiddle.net/OvPQ6J. Хотя исключение не выдается, большие куски XML-файла пропускаются в результате чего в коллекции <ChildItems>
отсутствуют записи.
Проблема в том, что ваш ReadXml()
не продвигается XmlReader
после конца соответствующего элемента, как требуется в документации (выделение добавлено):
Метод ReadXml должен воссоздать ваш объект, используя информацию, записанную методом WriteXml
.
Когда вызывается этот метод, читатель помещается в начальный тег, который оборачивает информацию для вашего типа. То есть непосредственно на начальном теге, который указывает начало сериализованного объекта. Когда этот метод возвращается, он должен прочитать весь элемент от начала до конца, включая все его содержимое. В отличие от метода WriteXml
, каркас не обрабатывает элемент-оболочку автоматически. Ваша реализация должна сделать это. Несоблюдение этих правил позиционирования может привести к тому, что код сгенерирует непредвиденные исключения во время выполнения или поврежденные данные.
Таким образом, неожиданные, случайные исключения являются документированным возможным следствием неправильной реализации ReadXml()
. Для получения дополнительной информации см. Правильный способ реализации IXmlSerializable? и Как правильно реализовать IXmlSerializable .
Поскольку эту ошибку легко совершить, ее можно систематически избежать, либо заключив в скобки логику чтения XML при вызове ReadSubtree()
или загрузив весь XML в память с помощью XNode.ReadFrom()
, например используя один из следующих двух базовых классов:
public abstract class StreamingXmlSerializableBase : IXmlSerializable
{
// Populate the object with the XmlReader returned by ReadSubtree
protected abstract void Populate(XmlReader reader);
public XmlSchema GetSchema() => null;
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
// Consume all child nodes of the current element using ReadSubtree()
using (var subReader = reader.ReadSubtree())
{
subReader.MoveToContent();
Populate(subReader);
}
reader.Read(); // Consume the end element itself.
}
public abstract void WriteXml(XmlWriter writer);
}
public abstract class XmlSerializableBase : IXmlSerializable
{
// Populate the object with an XElement loaded from the XmlReader for the current node
protected abstract void Populate(XElement element);
public XmlSchema GetSchema() => null;
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
var element = (XElement)XNode.ReadFrom(reader);
Populate(element);
}
public abstract void WriteXml(XmlWriter writer);
}
А вот исправленная версия вашего SerializableClass
, которая использует XmlSerializableBase
:
public class SerializableClass : XmlSerializableBase
{
public string Title { get; set; } = "Test title";
public string Description { get; set; } = "Super description";
public int Number { get; set; } = (int)(DateTime.Now.Ticks % 99);
protected override void Populate(XElement element)
{
this.Title = (string)element.Element(nameof(this.Title));
this.Description = (string)element.Element(nameof(this.Description));
// Leave Number unchanged if not present in the XML
this.Number = (int?)element.Element(nameof(this.Number)) ?? this.Number;
}
public override void WriteXml(XmlWriter writer)
{
writer.WriteStartElement(nameof(this.Title));
writer.WriteString(this.Title);
writer.WriteEndElement();
writer.WriteStartElement(nameof(this.Description));
writer.WriteString(this.Description);
writer.WriteEndElement();
writer.WriteStartElement(nameof(this.Number));
// Do not use ToString() as it is locale-dependent.
// Instead use XmlConvert.ToString(), or just writer.WriteValue
writer.WriteValue(this.Number);
writer.WriteEndElement();
}
}
Примечания:
В исходном коде вы пишете целое число Number
в XML в виде строки, используя ToString()
:
writer.WriteString(this.Number.ToString());
Это может вызвать проблемы, так как возвращение ToString()
может зависеть от локали. Вместо этого используйте XmlConvert.ToString(Int32)
или просто XmlWriter.WriteValue(Int32)
.
XmlReader.ReadSubtree()
оставляет XmlReader
с позицией на EndElement
узле читаемого элемента, тогда как XNode.ReadFrom()
оставляет считыватель с позицией сразу после EndElement
узел читаемого элемента. Это составляет дополнительный вызов Read()
в StreamingXmlSerializableBase.ReadXml()
.
Код, который вручную читает XML с использованием XmlReader
, всегда должен подвергаться модульному тестированию как с отформатированным, так и с неформатированным XML, поскольку определенные ошибки могут возникать только с одним или другим. (См., Например, этот ответ и этот ответ также для примеров такого.)
Пример рабочей .Net скрипки здесь: https://dotnetfiddle.net/s9OJOQ.