XmlReader 在第一个数组 属性 后停止反序列化

XmlReader stops deserializing after first array property

我有一个自定义列表,它曾经具有 BindingList<T> 的特性,同时也是 XML 可序列化的。 SBindingList<T>class如下:

public class SBindingList<T> : BindingList<T>, IXmlSerializable
{
    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        reader.Read();
        XmlSerializer serializer = new XmlSerializer(typeof(List<T>));
        List<T> ts = (List<T>)serializer.Deserialize(reader);
        foreach (T item in ts)
        {
            Add(item);
        }
    }

    public void WriteXml(XmlWriter writer)
    {
        XmlSerializer serializer = new XmlSerializer(typeof(List<T>));
        serializer.Serialize(writer, this.ToList());
    }
}

想法是像在幕后 List<T> 一样读写 XML,但仍然像 BindingList<T> 一样工作和运行。我遇到的问题是它开始读取 <Games> 然后当它到达 Add(item); 时开始反序列化 <AvailableQuests>。因为 <AvailableQuests> 是空的,它刚从 ReadXml 中掉出来,但立即返回完成添加 <Games> 并且刚从 ReadXml 中掉出来,甚至没有触及 <Characters><ActiveQuests><Games><AvailableQuests><Characters><ActiveQuests> 都是 SBindingList<T>。另请注意,出于隐私原因,我编辑了所有 ID。只需将 [blah] 标签替换为 ulong.

<Games>
    <ArrayOfGame xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <Game GuildID="[GuildID]" BotChannel="0" QuestChannel="0">
            <AvailableQuests>
                <ArrayOfQuest />
            </AvailableQuests>
            <Characters>
                <ArrayOfCharacter>
                    <Character Name="The Real Dirty Dan" Class="Meme" Level="17"
                        OwnerID="[Owner1]" Busy="false" />
                    <Character Name="Bob" Class="Builder" Level="2" OwnerID="[Owner2]"
                        Busy="false" />
                </ArrayOfCharacter>
            </Characters>
            <ActiveQuests>
                <ArrayOfQuest />
            </ActiveQuests>
        </Game>
    </ArrayOfGame>
</Games>

这是对象设置,以防有人需要它:

public class Game
{
    /// <summary>
    /// Only for serialization
    /// </summary>
    public Game() { }

    public Game(ulong guildID)
    {
        GuildID = guildID;
    }

    /// <summary>
    /// What discord server is the game on
    /// </summary>
    [XmlAttribute]
    public ulong GuildID { get; set; }

    [XmlAttribute]
    public ulong BotChannel { get; set; }

    [XmlAttribute]
    public ulong QuestChannel { get; set; }

    public SBindingList<Quest> AvailableQuests { get; set; } = new SBindingList<Quest>();

    public SBindingList<Character> Characters { get; set; } = new SBindingList<Character>();

    public SBindingList<Quest> ActiveQuests { get; set; } = new SBindingList<Quest>();

    public string Test { get; set; } // This gets ignored too

    public Character GetCharacterByName(string name)
    {
        return (from c in Characters
                where c.Name == name
                select c).FirstOrDefault();
    }
}

我真的不知道从哪里开始。我尝试过使用 List<T> 或一次只读取每个 T 一个,但这两种方式最终都会忽略所有其他元素。我唯一的猜测是我需要先用 reader 清理一些东西,然后才能让它像 reader.FinishedReading().

一样从 ReadXml 中掉出来

尝试以下操作:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

namespace ConsoleApplication23
{
    class Program
    {
        const string FILENAME = @"c:\temp\test.xml";
        static void Main(string[] args)
        {
            XmlReader reader = XmlReader.Create(FILENAME);
            XmlSerializer serializer = new XmlSerializer(typeof(Games));
            Games games = (Games)serializer.Deserialize(reader);
        }
    }
    public class Games
    {
        [XmlArray(ElementName = "ArrayOfGame")]
        [XmlArrayItem(ElementName = "Game")]
        public List<Game> game { get; set; }
 
    }
    public class Game
    {
        [XmlAttribute()]
        public string GuildID { get; set; }
        [XmlAttribute()]
        public int BotChannel { get; set; }
        [XmlAttribute()]
        public int QuestChannel { get; set; }
        [XmlArray(ElementName = "AvailableQuests")]
        [XmlArrayItem(ElementName = "ArrayOfQuest")]
        public List<Quest> availableQuest { get; set; }
        [XmlElement(ElementName = "Characters")]
        public Characters characters { get; set; }
        [XmlArray(ElementName = "ActiveQuests")]
        [XmlArrayItem(ElementName = "ArrayOfQuest")]
        public List<Quest> activeQuest { get; set; }
        public class Quest
    {
    }
    public class Characters
    {
        [XmlArray(ElementName = "ArrayOfCharacter")]
        [XmlArrayItem(ElementName = "Character")]
        public List<Character> character { get; set; }
    }
    public class Character
    {
        [XmlAttribute()]
        public string Name { get; set; }
        [XmlAttribute()]
        public string Class { get; set; }
        [XmlAttribute()]
        public int Level { get; set; }
        [XmlAttribute()]
        public string OwnerID { get; set; }
        [XmlAttribute()]
        public Boolean Busy{ get; set; }
    }
    public class ArrayOfQuest
    {
    }

}}

如果 reader 在除下一个元素之外的任何其他元素上,则 reader 放弃反序列化对象的其余部分。换句话说,reader 必须在下一个元素(在本例中为 Character 元素)上才能继续反序列化 <Game> 标记。在这种情况下,可以使用 reader.Skip() 然后 reader.ReadEndElement()

来修复

答案在于 the documentation for ReadXml:

When this method is called, the reader is positioned on the start tag that wraps the information for your type. That is, directly on the start tag that indicates the beginning of a serialized object. When this method returns, it must have read the entire element from beginning to end, including all of its contents. Unlike the WriteXml method, the framework does not handle the wrapper element automatically.

您似乎已经通过在开始时调用 reader.Read() 来发现这一点(通过开始标记并进入内容),但您没有在结束时发现这一点。解决方法是之后再次调用 reader.Read()

也许更好的方法是先调用 reader.ReadStartElement(),然后再调用 reader.ReadEndElement()。这只是在内部调用 reader.Read(),但如果 reader 未处于预期状态,它会抛出异常。

我还建议共享一个静态序列化程序实例,而不是每次读取或写入列表时都创建一个新实例。所以这应该有效:

public class SBindingList<T> : BindingList<T>, IXmlSerializable
{
    private static readonly XmlSerializer Serializer = new XmlSerializer(typeof(List<T>));
    
    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        reader.ReadStartElement();

        var items = (List<T>)Serializer.Deserialize(reader);
        
        foreach (T item in items)
        {
            Add(item);
        }

        reader.ReadEndElement();
    }

    public void WriteXml(XmlWriter writer)
    {
        Serializer.Serialize(writer, this.ToList());
    }
}

reader 需要位于原始对象中的下一个元素(在本例中为 <Characters>),否则它只是 returns(如果它位于结束元素节点)。这个解决方案不是万无一失的,也不是最干净的,但它完成了工作。如果我找到更好的东西来确认它在掉落之前位于下一个元素,我将编辑此答案。

public void ReadXml(XmlReader reader)
{
    reader.Read();
    XmlSerializer serializer = new XmlSerializer(typeof(List<T>));
    XmlReader xmlReader = reader.ReadSubtree();
    xmlReader.Read();
    List<T> ts = (List<T>)serializer.Deserialize(xmlReader);
    foreach (T item in ts)
    {
        Add(item);
    }
    xmlReader.Close();
    reader.Skip();
    reader.ReadEndElement();
}

关键是reader.Skip()reader.ReadEndElement()行。 Skip() 函数将 reader 移动到 </AvailableQuests> 并且 ReadEndElement() 确保它是一个结束元素(就像它需要的那样)并移动到 <Characters> .

编辑:使用