为什么我的 XML 反序列化仅识别第一个包含的对象标签

问题描述 投票:0回答:1

我有以下课程:

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();
    }
}

命名空间可以包含变量或其他命名空间,变量可以保存

Sysetem.TypeCode
中定义的多种类型之一,并且我需要能够保存整个嵌套结构。
WriteXml
方法按预期工作:

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 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
。如果我交换定义中变量和命名空间的顺序,我会得到命名空间的名称,但不会得到变量。

我做错了什么?我尝试在

reader.Read()
循环体之前和之后添加
while
调用,这只会导致解串器认为 XML 已损坏。

有趣的是,我可以添加任意数量的

<Alias>
标签,而不会破坏任何内容。

c# xml-serialization
1个回答
0
投票

正如 @jdweng 在评论中所建议的,我通过使用 XML Linq 找到了一个可行的解决方案。

我重写了

ReadXml
方法如下:

void IXmlSerializable.ReadXml(XmlReader reader)
{
    if (!reader.IsStartElement()) return;
    Name = reader.GetAttribute("name");

    var element = (XElement)XElement.ReadFrom(reader);

    foreach (var e in element.Elements().Cast<XElement>())
    {
        if (e.Name == "Alias")
        {
            _aliases.Add(e.Value);
        }
        else if (e.Name == "Variable")
        {
            var name = e.Attribute(XName.Get("name")).Value;
            var type = (TypeCode)Enum.Parse(typeof(TypeCode), e.Attribute(XName.Get("typecode")).Value);
            var valueStr = e.Value;

            var variable = new Variable(type, name);
            variable.SetValueFromString(valueStr);

            Add(variable);
        }
        else if (e.Name == "Namespace")
        {
            var ns = (Namespace)new XmlSerializer(typeof(Namespace)).Deserialize(e.CreateReader());
            Add(ns);
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.