2015-11-12 4 views
3

Je voudrais implémenter ISerializable pour une classe C# qui contient une liste d'enfants typés similaires. Prenons l'exemple suivant:ISerializable avec les enfants récursifs

using System; 
using System.Collections.Generic; 
using System.IO; 
using System.Runtime.Serialization; 
using System.Runtime.Serialization.Formatters.Binary; 

namespace serialisation 
{ 
    [Serializable] 
    internal class Nested : ISerializable 
    { 
     public string Name { get; set; } 

     public List<Nested> Children { get; set; } 

     public Nested(string name) 
     { 
      Name = name; 
      Children = new List<Nested>(); 
     } 

     protected Nested(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) 
     { 
      Name = info.GetString("Name"); 

      // This doesn't work: 
      Nested[] children = (Nested[])info.GetValue("Children", typeof(Nested[])); 
      Children = new List<Nested>(children); 

      // This works: 
      // Children = (List<Nested>)info.GetValue("Children", typeof(List<Nested>)); 
     } 

     public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) 
     { 
      info.AddValue("Name", Name); 

      // This doesn't work: 
      info.AddValue("Children", Children.ToArray()); 

      // This works: 
      //info.AddValue("Children", Children); 
     } 
    } 

    internal class Program 
    { 
     private static void Main(string[] args) 
     { 
      // Generate a hierarchy 
      Nested root = new Nested("root"); 
      Nested child1 = new Nested("child1"); 
      Nested child2 = new Nested("child2"); 
      Nested child3 = new Nested("child3"); 
      child1.Children.Add(child2); 
      child1.Children.Add(child3); 
      root.Children.Add(child1); 

      Nested deserialized; 
      BinaryFormatter binaryFmt = new BinaryFormatter(); 

      // Serialize 
      using (var fs = new FileStream("Nested.xml", FileMode.OpenOrCreate)) 
      { 
       binaryFmt.Serialize(fs, root); 
      } 

      // Deserialize 
      using (var fs = new FileStream("Nested.xml", FileMode.OpenOrCreate)) 
      { 
       deserialized = (Nested)binaryFmt.Deserialize(fs); 
      } 

      // deserialized.Children contains one null child 
      Console.WriteLine("Original Name: {0}", root.Name); 
      Console.WriteLine("New Name: {0}", deserialized.Name); 
     } 
    } 
} 

Dans l'exemple ci-dessus, Nested.GetObjectData et le constructeur de sérialiseur pour emboîtés sont invoquées 4 fois, l'un après l'autre.

L'ajout des enfants au sérialiseur en tant que tableau imbriqué renvoie un tableau correctement dimensionné lors de la désérialisation, mais tous les éléments seront null. Cependant, changer le type de tableau imbriqué en liste imbriquée corrigera magiquement les éléments nuls après l'appel des constructeurs pour les enfants.

Ce que je voudrais savoir est:

  1. Quelle est la particularité Nested Liste?
  2. Quelle est la méthode recommandée pour sérialiser une classe avec une structure récursive comme celle-ci?

Mise à jour:

Il semble qu'il y ait une interface supplémentaire, IDeserializationCallback.OnDeserialization, qui est appelée après désérialisation a eu lieu (l'ordre d'appel est non-déterministe). Vous pouvez stocker le tableau désérialisé dans une variable de membre temp dans le constructeur, puis l'affecter à une liste dans cette méthode. À moins que je ne manque quelque chose, cela semble moins qu'idéale car vous devez encombrer votre implémentation avec des vars temp.

+0

Je crois que c'est une question similaire qui a été répondu: http://stackoverflow.com/questions/4339602/désérialisation-d'un tableau-toujours-donne-un-tableau-de-null –

+0

J'ai aussi trouvé la même question il y a quelques minutes. Je ne devais pas être assez spécifique avec mes termes de recherche précédents ... – ChocolatePocket

Répondre

0

Je voudrais aller avec le modèle composite. La solution ci-dessous résout à la fois le BinaryFormatter (comme dans votre Main) et l'approche XmlSerializer, si vous deviez l'utiliser à la place. Composite et Component remplacez votre classe Nested.

[Serializable()] 
[XmlRoot("component", Namespace="", IsNullable=false)] 
public partial class CT_Component 
{ 
    [XmlAttribute("name")] 
    public string Name { get; set;} 
} 

[Serializable()] 
[XmlRoot("composite", Namespace="", IsNullable=false)] 
public partial class CT_Composite 
{ 
    [XmlElement("component", typeof(CT_Component))] 
    [XmlElement("composite", typeof(CT_Composite))] 
    public object[] Items { get; set; } 

    [XmlAttribute("name")] 
    public string Name { get; set; } 
} 

J'ai créé ceux de la xsd suivante, je vais toujours de xsd aux classes générées depuis que je ne peux jamais obtenir le droit de décoration d'attribut. L'essentiel de c'est le type CT_Composite récursive:

<xs:element name="component" type="CT_Component" /> 
<xs:element name="composite" type="CT_Composite" /> 
<xs:complexType name="CT_Component"> 
    <xs:attribute name="name" type="xs:string" use="required" /> 
</xs:complexType> 
<xs:complexType name="CT_Composite" > 
    <xs:choice minOccurs="1" maxOccurs="unbounded"> 
    <xs:element ref="component" /> 
    <xs:element name="composite" type="CT_Composite" /> 
    </xs:choice> 
    <xs:attribute name="name" type="xs:string" use="required" /> 
</xs:complexType> 

code sérialisation est le même. Déclaration de la variable:

var composite = new CT_Composite() { 
      Name = "root", 
      Items = new object[] { 
       new CT_Composite() { 
        Name = "child1", 
        Items = new object[] { 
         new CT_Component() {Name="child2"}, 
         new CT_Component() {Name="child3"} 
        } } } }; 

Si vous êtes encore plus orthodoxes sur le modèle, vous pouvez utiliser:

[Serializable()] 
[XmlRoot("component", Namespace="", IsNullable=false)] 
public class Component { 
    [XmlAttribute("name")] public string Name { get; set;} 
} 

[Serializable()] 
[XmlRoot("composite", Namespace="", IsNullable=false)] 
public class Composite : Component { 
    [XmlElement("component", typeof(Component))] 
    [XmlElement("composite", typeof(Composite))] 
    public object[] Items { get; set; } 
}