2017-03-08 6 views
1

IXmlSerializableを実装するクラスがあります。このクラスにはいくつかのプロパティが含まれています。シリアライズとデシリアライズは、クラスの1つのインスタンスを正常に動作します。しかし、クラスのコレクションの場合、シリアライゼーションは正常に動作しますが、デシリアライゼーションは永遠に実行されます。ここにコードスニペットがあります。私は.Net 4.6.2を使用しています。IXmlSerializableを実装する型のデシリアライズコレクションは永遠に実行されます

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

答えて

1

あなたの問題はdocumentationで説明したように、ReadXml()はそのラッパー要素だけでなく、その内容を消費しなければならない、ということである:

ReadXml方法は、によって書かれた情報を使用してオブジェクトを再構築しなければなりませんWriteXmlメソッド。

このメソッドが呼び出されると、リーダーは、タイプの情報をラップする開始タグの上に配置されます。つまり、直列化されたオブジェクトの先頭を示す開始タグの直上にあります。 このメソッドが返ってくると、すべての内容を含め、要素全体を最初から最後まで読み取る必要があります。 WriteXmlメソッドとは異なり、フレームワークはラッパー要素を自動的に処理しません。実装ではそうする必要があります。これらの配置ルールを守らないと、予期しないランタイム例外が発生したり、データが破損する可能性があります。

MyClass.ReadXml()MyClassオブジェクトは、ルート要素としてシリアル化されない場合に無限ループを引き起こす、これを実行されません。

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

今すぐあなたの<MyClass>要素が入れ子になったか、オプションの要素を持つ非常に簡単です:代わりに、あなたのMyClassはこのような何かを見なければなりません。より複雑なカスタムのシリアライゼーションには、ReadXml()メソッドの正確な読み込みを保証するために採用できる2つの戦略があります。

まず、XNode.ReadFrom()を呼び出して、現在の要素をXElementに読み込むことができます。

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

第二に、あなたが必要なXMLコンテンツが消費されていることを確認するためにXmlReader.ReadSubtree()を使用することができます:

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)); 
    } 
} 
これはで動作するように簡単に XmlReaderから直接解析するよりも少し多くのメモリが必要ですが、 ずっとです

いくつかの最終ノート:

  • <MyClass />と01の両方を処理するようにしてください。これらの2つの形式は意味的に同一であり、送信側のシステムでどちらかを選択できます。

  • XmlConvertクラスから、XMLとのプリミティブを変換する方法を推奨します。これにより、国際化が正しく処理されます。

  • インデントの有無にかかわらずテストしてください。場合によってはReadXml()メソッドが余分なXMLノードを消費しますが、インデントが有効になっていると、バグは隠されます。

  • How to Implement IXmlSerializable Correctlyを参照してください。

関連する問題