2017-03-08 1 views
1

J'ai une classe implémentant IXmlSerializable. Cette classe contient quelques propriétés. Sérialiser et désérialiser une seule instance de la classe fonctionne correctement. Mais en cas de collection de la classe, la sérialisation fonctionne bien mais la désérialisation fonctionne pour toujours. Voici un extrait de code. J'utilise .Net 4.6.2.La désérialisation de la collection de types implémentant IXmlSerializable s'exécute pour toujours

public class MyClass : IXmlSerializable 
{ 
    public int A { get; set; } 
    public int B { get; set; } 

    public XmlSchema GetSchema() 
    { 
     return null; 
    } 

    public void ReadXml(XmlReader reader) 
    { 
     this.A = Convert.ToInt32(reader.GetAttribute("A")); 
     this.B = Convert.ToInt32(reader.GetAttribute("B")); 
    } 

    public void WriteXml(XmlWriter writer) 
    { 
     writer.WriteAttributeString("A", this.A.ToString()); 
     writer.WriteAttributeString("B", this.B.ToString()); 
    } 
} 
class Program 
{ 
    static void Main(string[] args) 
    { 
     var instance = new MyClass { A = 1, B = 2 }; 
     Serialize(instance); 
     instance = Deserialize<MyClass>();//works fine 

     var list = new List<MyClass> { new MyClass { A = 10, B = 20 } }; 
     Serialize(list); 
     list = Deserialize<List<MyClass>>();//runs forever 
    } 

    private static void Serialize(object o) 
    { 
     XmlSerializer ser = new XmlSerializer(o.GetType()); 
     using (TextWriter writer = new StreamWriter("xml.xml", false, Encoding.UTF8)) 
     { 
      ser.Serialize(writer, o); 
     } 
    } 

    private static T Deserialize<T>() 
    { 
     XmlSerializer ser = new XmlSerializer(typeof(T)); 
     using (TextReader reader = new StreamReader("xml.xml")) 
     { 
      return (T)ser.Deserialize(reader); 
     } 
    } 
} 

Répondre

1

Votre problème est que, comme cela est expliqué dans le documentation, ReadXml() doit consommer son élément d'emballage ainsi que son contenu:

La méthode ReadXml doit reconstituer votre objet en utilisant l'information qui a été écrit par la méthode WriteXml. Lorsque cette méthode est appelée, le lecteur est positionné sur l'étiquette de début qui enveloppe les informations pour votre type. C'est-à-dire, directement sur la balise de démarrage qui indique le début d'un objet sérialisé. Lorsque cette méthode retourne, elle doit avoir lu l'intégralité de l'élément du début à la fin, y compris tout son contenu. Contrairement à la méthode WriteXml, l'infrastructure ne gère pas automatiquement l'élément wrapper. Votre implémentation doit le faire. Si vous ne respectez pas ces règles de positionnement, le code peut générer des exceptions d'exécution inattendues ou corrompre des données.

MyClass.ReadXml() ne le fait pas, ce qui provoque une boucle infinie lorsque l'objet n'a pas été sérialisé MyClass que l'élément racine. Au lieu de cela, votre MyClass doit ressembler à quelque chose comme ceci:

public class MyClass : IXmlSerializable 
{ 
    public int A { get; set; } 
    public int B { get; set; } 

    public XmlSchema GetSchema() 
    { 
     return null; 
    } 

    public void ReadXml(XmlReader reader) 
    { 
     /* 
     * https://msdn.microsoft.com/en-us/library/system.xml.serialization.ixmlserializable.readxml.aspx 
     * 
     * When this method is called, the reader is positioned at the start of the element that wraps the information for your type. 
     * That is, just before the start tag that indicates the beginning of a serialized object. When this method returns, 
     * it must have read the entire element from beginning to end, including all of its contents. Unlike the WriteXml method, 
     * the framework does not handle the wrapper element automatically. Your implementation must do so. Failing to observe these 
     * positioning rules may cause code to generate unexpected runtime exceptions or corrupt data. 
     */ 
     var isEmptyElement = reader.IsEmptyElement; 
     this.A = XmlConvert.ToInt32(reader.GetAttribute("A")); 
     this.B = XmlConvert.ToInt32(reader.GetAttribute("B")); 
     reader.ReadStartElement(); 
     if (!isEmptyElement) 
     { 
      reader.ReadEndElement(); 
     } 
    } 

    public void WriteXml(XmlWriter writer) 
    { 
     writer.WriteAttributeString("A", XmlConvert.ToString(this.A)); 
     writer.WriteAttributeString("B", XmlConvert.ToString(this.B)); 
    } 
} 

Maintenant, votre élément <MyClass> est très simple sans éléments imbriqués ou en option. Pour les sérialisations personnalisées plus complexes, il existe quelques stratégies que vous pouvez adopter pour garantir que votre méthode ReadXml() lit exactement ce qu'elle devrait, ni plus, ni moins.

Tout d'abord, vous pouvez appeler XNode.ReadFrom() pour charger l'élément en cours dans un XElement. Cela nécessite un peu plus de mémoire que l'analyse syntaxique directement à partir d'un XmlReader mais est beaucoup plus facile de travailler avec:

public class MyClass : IXmlSerializable 
{ 
    public int A { get; set; } 
    public int B { get; set; } 

    public XmlSchema GetSchema() 
    { 
     return null; 
    } 

    public void ReadXml(XmlReader reader) 
    { 
     var element = (XElement)XNode.ReadFrom(reader); 
     this.A = (int)element.Attribute("A"); 
     this.B = (int)element.Attribute("B"); 
    } 

    public void WriteXml(XmlWriter writer) 
    { 
     writer.WriteAttributeString("A", XmlConvert.ToString(this.A)); 
     writer.WriteAttributeString("B", XmlConvert.ToString(this.B)); 
    } 
} 

Deuxièmement, vous pouvez utiliser XmlReader.ReadSubtree() pour assurer le contenu XML requis est consommé:

public class MyClass : IXmlSerializable 
{ 
    public int A { get; set; } 
    public int B { get; set; } 

    public XmlSchema GetSchema() 
    { 
     return null; 
    } 

    protected virtual void ReadXmlSubtree(XmlReader reader) 
    { 
     this.A = XmlConvert.ToInt32(reader.GetAttribute("A")); 
     this.B = XmlConvert.ToInt32(reader.GetAttribute("B")); 
    } 

    public void ReadXml(XmlReader reader) 
    { 
     // Consume all child nodes of the current element using ReadSubtree() 
     using (var subReader = reader.ReadSubtree()) 
     { 
      subReader.MoveToContent(); 
      ReadXmlSubtree(subReader); 
     } 
     reader.Read(); // Consume the end element itself. 
    } 

    public void WriteXml(XmlWriter writer) 
    { 
     writer.WriteAttributeString("A", XmlConvert.ToString(this.A)); 
     writer.WriteAttributeString("B", XmlConvert.ToString(this.B)); 
    } 
} 

quelques notes finales:

  • Soyez sûr de gérer à la fois <MyClass /> et <MyClass></MyClass>. Ces deux formes sont sémantiquement identiques et un système d'envoi pourrait choisir soit.

  • Préférez les méthodes de la classe XmlConvert pour convertir les primitives de et vers XML. Cela permet une internationalisation correcte.

  • Assurez-vous de tester avec et sans indentation. Parfois, une méthode ReadXml() consomme un nœud XML supplémentaire, mais le bogue sera masqué lorsque l'indentation est activée, car c'est le nœud d'espace qui est mangé.Pour une lecture supplémentaire, voir How to Implement IXmlSerializable Correctly.