2017-07-18 12 views
0

我有一些树节点结构的类。它具有儿童属性,只读集合类型用于隐藏直接更改子项和添加子控件的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

任何特别的为什么它是一个'ReadOnlyList'?为什么不把它变成一个私人列表'然后公开可以添加到它的'AddChild'方法? – JonE

+0

你应该看看'ISerializable'接口。 https://msdn.microsoft.com/en-us/library/system.runtime.serialization.iserializable%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396 –

+1

@CihanYakar'XmlSerializer'不关心关于'ISerializable'和'IXmlSerializable'很难正确实现 –

回答

0

如果这是XmlSerializer,那么:不,你不能这样做,除非实现完全IXmlSerializable,这是非常正确地做,并且击败首先使用XmlSerializer的全部目的。

如果数据不是很大,那么我对表单“我现有的对象模型与我选择的序列化程序不兼容”的任何问题的默认答案是:当它变得杂乱时,会停止序列化您的现有对象型号。相反,创建一个单独的DTO模型,该模型设计为完全以与您选择的序列化器一起使用,并在序列化之前将数据映射到DTO模型中 - 然后再返回。这可能意味着在DTO模型中使用List<T>而不是IReadOnlyList<T>

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方法的参数必须是相同的类型从Current属性返回来自GetEnumerator,或该类型的基地之一返回的值。

因此,你的代理IEnumerable<T>包装实际上并不需要实现ICollection<T>其全套的方法,包括Clear()Remove()Contains()等。只要有正确的签名Add()就足够了。 (如果你想实现,比如说类似的解决方案,Json.NET,您的替代类型需要实现ICollection<T> - 但你可以只从不必要的方法,如Remove()Clear()抛出异常。)

其次,在Introducing XML Serialization说:

XML序列化不会转换方法,索引器,私有字段或只读属性(除只读的集合)

I.e. XmlSerializer可以成功反序列化预先分配的集合中的项目,即使该集合是由只读属性返回的。这样可以避免需要为代理属性或代理集合包装类型的默认构造函数实现set方法。

相关问题