我有以下课程:
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>
标签,而不会破坏任何内容。
正如 @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);
}
}
}