2017-07-18 4 views
0

J'ai une classe avec une structure de nœud d'arbre. Il a la propriété enfants avec le type de collection en lecture seule pour cacher le changement direct des enfants et la méthode AddChild (...) pour l'ajout des enfants de contrôle.Comment utiliser la méthode Custom Add pour le processus de désérialisation de la collection?

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); 
    } 
} 

Et je dois fournir la désérialisation pour ma classe. J'ai essayé:

[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; 
    } 
} 

Bien sûr, cela ne fonctionne pas car IReadOnlyList n'a pas de méthode Add.

Est-il possible de lier AddChild au processus de désérialisation? Et si 'oui' - Comment? Comment fournir le même niveau d'encapsulation avec la capacité de désérialisation?

+0

Pourquoi est-ce une ReadOnlyList? Pourquoi ne pas en faire une 'Liste privée ' et ensuite exposer la méthode 'AddChild' qui peut y ajouter? – JonE

+0

Vous devriez regarder l'interface 'ISerializable'. https://msdn.microsoft.com/en-us/library/system.runtime.serialization.iserializable%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396 –

+1

@CihanYakar 'XmlSerializer' ne s'en soucie pas à propos de 'ISerializable', et' IXmlSerializable' est très difficile à implémenter correctement –

Répondre

0

Si cela est XmlSerializer, alors: non, vous ne pouvez pas le faire à moins que vous mettre en œuvre IXmlSerializable complètement, ce qui est très difficile à faire correctement, et défaites tout l'objet de l'utilisation XmlSerializer en premier lieu.

Si les données ne sont pas énormes, alors ma réponse par défaut à tout problème de la forme "mon modèle d'objet existant ne fonctionne pas bien avec mon sérialiseur choisi" est: quand il devient désordonné, arrêter de sérialiser votre objet existant modèle. Au lieu de cela, créez un modèle DTO distinct qui est conçu seulement pour fonctionner correctement avec le sérialiseur choisi, et mapper les données dans le modèle DTO avant la sérialisation - et vice versa par la suite. Cela peut signifier utiliser List<T> dans le modèle DTO plutôt que IReadOnlyList<T>.

0

Cela peut être fait en ajoutant une propriété de substitution pour TreeNode qui retourne un type d'emballage de substitution qui implémente les deux IEnumerable<T> et Add(T) à l'aide des délégués fournis à son constructeur. Tout d'abord, introduire l'emballage de substitution suivant:

// 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 
} 

Ensuite, modifiez votre TreeNode en marquant Children avec [XmlIgnore] et en ajoutant une propriété de substitution qui renvoie un pré-alloué 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); 
    } 
} 

Votre type peut maintenant entièrement sérialisé et désérialisé par XmlSerializer. Travailler .NET fiddle.

Cette solution tire parti des comportements documentés suivants de XmlSerializer. Tout d'abord, comme indiqué dans Remarks for XmlSerializer:

XmlSerializer accorde un traitement spécial aux classes qui mettent en œuvre IEnumerable ou ICollection. Une classe qui implémente IEnumerable doit implémenter une méthode Add publique prenant un seul paramètre. Le paramètre de la méthode Add doit être du même type que celui de retour de la Current bien sur la valeur retournée par GetEnumerator, ou une des bases de ce type.

Ainsi votre emballage IEnumerable<T> de remplacement n'a pas besoin en fait de mettre en œuvre ICollection<T> avec son ensemble complet de méthodes, y compris Clear(), Remove(), Contains() et ainsi de suite. Juste un Add() avec la signature correcte est suffisante. (Si vous voulez mettre en œuvre une solution similaire pour, disons, Json.NET, votre type de remplacement aurait besoin de mettre en œuvre ICollection<T> - mais vous pouvez simplement lancer des exceptions des méthodes inutiles telles que Remove() et Clear().)

En second lieu, a déclaré dans Introducing XML Serialization:

sérialisation XML ne convertit pas les méthodes, les indexeurs, les champs privés ou propriétés en lecture seule (sauf en lecture seule collections).

I.e. XmlSerializer peut désérialiser avec succès les éléments dans une collection pré-allouée même lorsque cette collection est renvoyée par une propriété get-only. Cela évite d'avoir à implémenter une méthode set pour la propriété de substitution ou un constructeur par défaut pour le type d'encapsuleur de collection de substitution.