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.
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
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 –
@CihanYakar 'XmlSerializer' ne s'en soucie pas à propos de 'ISerializable', et' IXmlSerializable' est très difficile à implémenter correctement –