You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

XML反序列化仅识别首个嵌套对象标签的问题排查

问题

我有以下C#类:

public interface IVariable
{
    string Name { get; }
    dynamic Value { get; set; }
}

public class Namespace : IVariable, IXmlSerializable, IEnumerable<IVariable>
{
    protected Namespace() { }

    public Namespace(string name, params string[] aliases)
    {
        this.Name = name;
        this._aliases.AddRange(aliases);
    }

    private readonly List<IVariable> _variables = new List<IVariable>();

    public string Name { get; private set; }

    dynamic IVariable.Value { get => this; set => throw new InvalidOperationException(); }

    private readonly List<string> _aliases = new List<string>();

    public void Add(IVariable variable)
    {
        _variables.Add(variable);
    }
    
    public dynamic this[string key]
    {
        get
        {
            foreach (var variable in _variables)
            {
                if (variable.Name == key) 
                    return variable.Value;

                if (variable is Namespace ns && ns._aliases.Contains(key))
                    return ns;          
            }

            return null;
        }
        set
        {
            foreach (var variable in _variables)
            {
                if (variable.Name == key) 
                    variable.Value = value;

                if (variable is Namespace ns && ns._aliases.Contains(key))
                    throw new InvalidOperationException();
            }
        }
    }

    void IXmlSerializable.WriteXml(XmlWriter writer)
    {
        writer.WriteAttributeString("name", Name);

        foreach (var alias in _aliases)
        {
            writer.WriteElementString("Alias", alias);
        }

        foreach (var variable in _variables)
        {
            if (variable is Variable v)
            {
                writer.WriteStartElement("Variable");
                writer.WriteAttributeString("name", v.Name);
                writer.WriteAttributeString("typecode", v.TypeCode.ToString());
                writer.WriteString(v.Value.ToString());
                writer.WriteEndElement();
            }
            else
            {
                var serializer = new XmlSerializer(typeof(Namespace));
                serializer.Serialize(writer, (Namespace)variable);
            }
        }
    }

    XmlSchema IXmlSerializable.GetSchema()
    {
        return null;
    }

    void IXmlSerializable.ReadXml(XmlReader reader)
    {
        Name = reader.GetAttribute("name");

        var aliases = new List<string>();

        while (true)
        {
            reader.Read();

            if (reader.Name != "Alias") 
                break;

            reader.Read();
            _aliases.Add(reader.Value);
            reader.Read();
        }

        while (true)
        {
            if (reader.Name == "Variable")
            {
                var name = reader.GetAttribute("name");
                var typecode = (TypeCode)Enum.Parse(typeof(TypeCode), reader.GetAttribute("typecode"));
                reader.Read();
                var variable = new Variable(typecode, name);
                variable.SetValueFromString(reader.Value);

                Add(variable);
            }
            else if (reader.Name == "Namespace")
            {
                var serializer = new XmlSerializer(typeof(Namespace));
                _variables.Add((Namespace)serializer.Deserialize(reader));
            }
            else
            {
                break;
            }
        }
    }

    public IEnumerator<IVariable> GetEnumerator()
    {
        return ((IEnumerable<IVariable>)_variables).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable)_variables).GetEnumerator();
    }
}

Namespace可以包含Variable或其他Namespace,Variable能存储System.TypeCode定义的多种类型,我需要保存整个嵌套结构。WriteXml方法工作正常,能生成符合预期的XML:

void Main()
{
    var stream = new MemoryStream();
    var serializer = new XmlSerializer(typeof(Namespace));

    var ns = new Namespace("foo", "f")
    {
        new Variable(TypeCode.String, "bar", "baz"),
        new Namespace("qux")
        {
            new Variable(TypeCode.Int32, "quxx", 42)
        },
    };

    serializer.Serialize(stream, ns);

    stream.Position = 0;
    Console.Out.WriteLine(new StreamReader(stream).ReadToEnd());
}

生成的XML如下:

<?xml version="1.0" encoding="utf-8"?>
<Namespace name="foo">
    <Alias>f</Alias>
    <Variable name="bar" typecode="String">baz</Variable>
    <Namespace name="qux">
        <Variable name="quxx" typecode="Int32">42</Variable>
    </Namespace>
</Namespace>

但反序列化时:

stream.Position = 0;
var ns2 = (Namespace)serializer.Deserialize(stream);

foreach (IVariable variable in ns2)
{
    Console.Out.WriteLine(variable.Name);
}

我只能获取到第一个成员(比如bar),如果交换Variable和Namespace的定义顺序,就只能获取到Namespace名称而非Variable。我尝试在while循环体前后添加reader.Read()调用,却导致反序列化认为XML损坏。有意思的是,我可以添加任意数量的<Alias>标签而不会有问题。请问我哪里出错了?


解决方案

问题出在ReadXml方法的第二个while循环里——处理完第一个元素后,你没有正确移动XmlReader的指针到下一个元素,导致循环无法识别后续节点,直接跳出。

具体原因:

  1. 处理<Variable>后,XmlReader停留在</Variable>节点上,此时reader.Name仍为"Variable",但没有调用reader.Read()移动到下一个元素的起始标签,下一次循环会重复处理已结束的节点,最终触发else分支跳出。
  2. 处理<Namespace>时,serializer.Deserialize(reader)会自动将指针移到</Namespace>之后,但循环没有先调用reader.Read()定位到下一个元素的起始标签,导致reader.Name不是预期的元素名,直接跳出循环。

修改后的ReadXml方法如下:

void IXmlSerializable.ReadXml(XmlReader reader)
{
    Name = reader.GetAttribute("name");

    // 读取Alias节点
    while (reader.Read())
    {
        if (reader.NodeType == XmlNodeType.Element && reader.Name == "Alias")
        {
            reader.Read();
            _aliases.Add(reader.Value);
            // 移动到</Alias>节点
            reader.Read();
        }
        else
        {
            // 跳出Alias循环,准备读取Variable/Namespace
            break;
        }
    }

    // 读取Variable和Namespace节点
    while (true)
    {
        // 先移动到下一个元素节点,若不是元素则退出循环
        if (!reader.Read() || reader.NodeType != XmlNodeType.Element)
            break;

        if (reader.Name == "Variable")
        {
            var name = reader.GetAttribute("name");
            var typecode = (TypeCode)Enum.Parse(typeof(TypeCode), reader.GetAttribute("typecode"));
            reader.Read();
            var variable = new Variable(typecode, name);
            variable.SetValueFromString(reader.Value);

            Add(variable);
            // 移动到</Variable>节点
            reader.Read();
        }
        else if (reader.Name == "Namespace")
        {
            var serializer = new XmlSerializer(typeof(Namespace));
            _variables.Add((Namespace)serializer.Deserialize(reader));
            // Deserialize已自动将指针移到</Namespace>之后,无需手动Read
        }
        else
        {
            // 跳过未知元素,提升鲁棒性
            reader.Skip();
        }
    }
}

关键修改点:

  • 第一个循环改用reader.Read()作为条件,同时判断节点类型为XmlNodeType.Element,避免误判非元素节点。
  • 第二个循环开头先调用reader.Read()定位到下一个有效元素节点,确保每次循环处理的都是起始标签。
  • 处理完<Variable>后,手动调用reader.Read()移动到闭合标签之后,为下一次循环做准备。
  • 增加未知元素的跳过逻辑reader.Skip(),避免因意外节点导致反序列化失败。

修改后,反序列化就能正确读取所有嵌套的Variable和Namespace节点了。


内容的提问来源于stack exchange,提问作者RoadieRich

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 13:07:03