如何使用自定义Add方法进行集合反序列化过程?

时间:2017-07-18 10:42:39

标签: c# .net serialization deserialization encapsulation

我有一些树节点结构的类。它有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绑定到反序列化过程?如果'是' - 怎么样?

如何提供具有反序列化能力的相同封装级别?

2 个答案:

答案 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()方法,也无需为代理项集合包装类型实现默认构造函数。