【问题标题】:How to use custom Add method for collection deserialization process?如何使用自定义 Add 方法进行集合反序列化过程?
【发布时间】:2017-12-23 04:28:06
【问题描述】:

我有一些具有树节点结构的类。它具有用于隐藏直接更改子项的只读集合类型的 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 绑定到反序列化过程?如果“是” - 如何?

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

【问题讨论】:

  • 为什么它是ReadOnlyList?为什么不直接将其设为private List&lt;T&gt;,然后公开可以添加到其中的AddChild 方法?
  • 你应该看ISerializable界面。 msdn.microsoft.com/en-us/library/…
  • @CihanYakar XmlSerializer 不关心ISerializable,而IXmlSerializable 很难正确实现

标签: c# .net serialization deserialization encapsulation


【解决方案1】:

这可以通过向TreeNode 添加一个代理属性来完成,该属性返回一个代理包装类型,该类型使用提供给其构造函数的委托实现IEnumerable&lt;T&gt;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&lt;TreeNode&gt; 的代理属性:

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

因此,您的代理 IEnumerable&lt;T&gt; 包装器实际上不需要实现 ICollection&lt;T&gt; 及其全套方法,包括 Clear()Remove()Contains() 等。只需带有正确签名的Add() 就足够了。 (如果您想为 Json.NET 实现类似的解决方案,您的代理类型将需要实现 ICollection&lt;T&gt; - 但您可以从不必要的方法中抛出异常,例如 Remove()Clear()。)

其次,如Introducing XML Serialization中所述:

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

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

【讨论】:

    【解决方案2】:

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

    如果数据不是很大,那么我对“我现有的对象模型不适用于我选择的序列化程序”形式的任何问题的默认答案是:当它变得混乱时,停止序列化你现有的对象模型。相反,请创建一个单独的 DTO 模型,该模型与您选择的序列化程序一起工作,并在序列化之前将数据映射到 DTO 模型 - 之后再返回。这可能意味着在 DTO 模型中使用 List&lt;T&gt; 而不是 IReadOnlyList&lt;T&gt;

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-04
      • 2019-10-19
      • 1970-01-01
      • 2013-09-09
      • 1970-01-01
      • 1970-01-01
      • 2022-01-03
      • 2015-01-06
      相关资源
      最近更新 更多