我有一些树节点结构的类。它有Children属性,只读集合类型用于隐藏直接更改子项,AddChild(...)方法用于控制子项添加。
class TreeNode {
List<TreeNode> _children = new List<TreeNode>();
public IReadOnlyList<TreeNode> Children => children;
public string Name { get; set; } // some other filed
public void AddChild(TreeNode node){
// ... some code
_children.Add(node);
}
}
我需要为我的班级提供反序列化。我试过了:
[Serializable]
[XmlRoot(ElementName = "node")]
class TreeNode {
List<TreeNode> _children = new List<TreeNode>();
[XmlElement(ElementName = "node")]
public IReadOnlyList<TreeNode> Children => children;
[XmlAttribute(DataType = "string", AttributeName = "name")]
public string Name { get; set; } // some other filed
public void AddChild(TreeNode node){
// ... some code
_children.Add(node);
}
public static TreeNode Deserialize(Stream stream) {
var serializer = new XmlSerializer(typeof(TreeNode));
var obj = serializer.Deserialize(stream);
var tree = (TreeNode)obj;
return tree;
}
}
当然,这不起作用,因为IReadOnlyList没有Add方法。
是否可以将AddChild绑定到反序列化过程?如果'是' - 怎么样?
如何提供具有反序列化能力的相同封装级别?
答案 0 :(得分:0)
如果这是XmlSerializer
,那么:不,你不能这样做,除非你完全实施IXmlSerializable
,非常很难正确地做到,首先打败了使用XmlSerializer
的全部目的。
如果数据不是很大,那么我对表格的任何问题的默认答案都不适用于我选择的序列化程序&#34;是:当它变得混乱时,停止序列化现有的对象模型。相反,创建一个单独的DTO模型,该模型设计为完全,以便与所选的序列化器配合使用,并在序列化之前将数据映射到DTO模型中 - 之后再返回。这可能意味着在DTO模型中使用List<T>
而不是IReadOnlyList<T>
。
答案 1 :(得分:0)
这可以通过向TreeNode
添加一个代理属性来完成,该代理返回一个代理包装类型,该类型使用提供给其构造函数的委托来实现IEnumerable<T>
和Add(T)
。首先,介绍以下代理包装器:
// Proxy class for any enumerable with the requisite `Add` methods.
public class EnumerableProxy<T> : IEnumerable<T>
{
readonly Action<T> add;
readonly Func<IEnumerable<T>> getEnumerable;
// XmlSerializer required default constructor (which can be private).
EnumerableProxy()
{
throw new NotImplementedException("The parameterless constructor should never be called directly");
}
public EnumerableProxy(Func<IEnumerable<T>> getEnumerable, Action<T> add)
{
if (getEnumerable == null || add == null)
throw new ArgumentNullException();
this.getEnumerable = getEnumerable;
this.add = add;
}
public void Add(T obj)
{
// Required Add() method as documented here:
// https://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer%28v=vs.100%29.aspx
add(obj);
}
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
return (getEnumerable() ?? Enumerable.Empty<T>()).GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
接下来,通过使用TreeNode
标记Children
并添加返回预先分配的[XmlIgnore]
的代理属性来修改您的EnumerableProxy<TreeNode>
:
[XmlRoot(ElementName = "node")]
public class TreeNode
{
List<TreeNode> _children = new List<TreeNode>();
[XmlIgnore]
public IReadOnlyList<TreeNode> Children { get { return _children; } }
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
[XmlElement(ElementName = "node")]
public EnumerableProxy<TreeNode> ChildrenSurrogate
{
get
{
return new EnumerableProxy<TreeNode>(() => _children, n => AddChild(n));
}
}
[XmlAttribute(DataType = "string", AttributeName = "name")]
public string Name { get; set; } // some other filed
public void AddChild(TreeNode node)
{
// ... some code
_children.Add(node);
}
}
您的类型现在可以由XmlSerializer
完全序列化和反序列化。工作.NET fiddle。
此解决方案利用XmlSerializer
的以下记录行为。首先,如Remarks for XmlSerializer
中所述:
XmlSerializer为实现IEnumerable或ICollection的类提供特殊处理。实现IEnumerable的类必须实现一个带有单个参数的公共
Add
方法。Add
方法的参数必须与从 {{1}返回的值Current
属性返回的参数类型相同}} ,或其中一种类型的基础。
因此,您的代理GetEnumerator
包装器实际上并不需要使用其完整的方法来实现ICollection<T>
,包括Clear()
,Remove()
,Contains()
等等。仅具有正确签名的IEnumerable<T>
就足够了。 (如果你想为Json.NET实现类似的解决方案,你的代理类型需要实现Add()
- 但你可以从不必要的方法中抛出异常,例如ICollection<T>
和{{ 1}}。)
其次,如Introducing XML Serialization中所述:
XML序列化不会转换方法,索引器,私有字段或只读属性(只读集合除外)。
即。 Remove()
可以成功反序列化预分配集合中的项目,即使该集合由get-only属性返回也是如此。这样就无需为代理项属性实现Clear()
方法,也无需为代理项集合包装类型实现默认构造函数。